No description
Find a file
2026-09-12 06:40:45 +02:00
modules Init 2026-09-12 06:15:08 +02:00
.envrc Init 2026-09-12 06:15:08 +02:00
.gitignore Init 2026-09-12 06:15:08 +02:00
flake.lock Init 2026-09-12 06:15:08 +02:00
flake.nix Init 2026-09-12 06:15:08 +02:00
module.nix Export the OTLP endpoint from services.ltgm 2026-09-12 06:40:45 +02:00
README.md Export the OTLP endpoint from services.ltgm 2026-09-12 06:40:45 +02:00

ltgm

Local LGTM observability stack as devenv services. Nothing here is project-specific: any service on the machine points at the OTLP endpoint and shows up in Grafana.

Running it standalone

devenv up          # or: nix develop --impure, then devenv up

Grafana comes up on http://127.0.0.1:3000 with anonymous viewer access (admin/admin to edit). State lives in .devenv/state/; delete it to start over.

Using it from another devenv project

Import it as plain source, not as a flake:

inputs.ltgm = {
  url = "github:you/ltgm";
  flake = false;
};

devShells.default = devenv.lib.mkShell {
  inherit inputs pkgs;
  modules = [
    "${inputs.ltgm}/module.nix"
    { services.ltgm.enable = true; }
  ];
};

The five processes then join that project's devenv up. Every port is declared through devenv's port allocator, and the wiring — Alloy's fan-out, the Grafana datasources — reads the allocated value rather than the requested one.

Ports only relocate under the devenv CLI; through the flake integration they stay as configured, so set them explicitly if they collide with something you already run.

flake = false matters. The modules take pkgs, lib and config from whoever evaluates them, so they build against your nixpkgs and need nothing from this repo's inputs — but taking ltgm as a flake input pulls its whole lock into yours, including a second nixpkgs and all of devenv's transitive inputs. As plain source, this repo's flake.lock is ignored; it only pins the shell used to develop ltgm itself.

devenvModules.default is exported too, if you would rather have the flake output and pin ltgm's inputs with follows.

Ports

Component Port Notes
Alloy 4317 / 4318 OTLP gRPC / HTTP — the only entry point
Alloy 12345 Alloy's own UI
Grafana 3000 UI
Prometheus 9090 remote-write receiver enabled
Loki 3100 push API
Tempo 3200 / 14317 HTTP / OTLP gRPC (Alloy owns 4317)

Alloy is the single OTLP entry point: services export to 4317/4318 and know nothing about Tempo, Loki or Prometheus, so swapping a backend is a change in module.nix rather than in every service.

Pointing a service at it

Inside the shell there is nothing to wire: services.ltgm exports OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_PROTOCOL itself, built from the port Alloy actually got. Name your service and you are done:

OTEL_SERVICE_NAME=<service>

Both are mkDefault, so setting either in your own env wins. If you need the value in Nix — a differently named variable, a process argument — read it from config.services.ltgm.otlp.endpoint (or .grpcEndpoint) rather than rebuilding the string.

From outside the shell, export to http://127.0.0.1:4318 with http/protobuf.

Layout

  • module.nix — services.ltgm, which only wires the components together: Alloy's fan-out and Grafana's datasources, pointed at allocated ports
  • modules/{loki,tempo,alloy,grafana}.nix — one standalone devenv service each, usable on their own and knowing nothing about each other. Prometheus is devenv's own services.prometheus.

Config files are generated from Nix: settings is a freeform attrset merged over each module's defaults (pkgs.formats.yaml for Loki and Tempo, formats.ini for Grafana). Alloy is the exception — River has no nixpkgs formatter, so services.alloy.configuration takes lines, the same shape devenv's nginx service uses.