| modules | ||
| .envrc | ||
| .gitignore | ||
| flake.lock | ||
| flake.nix | ||
| module.nix | ||
| README.md | ||
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 portsmodules/{loki,tempo,alloy,grafana}.nix— one standalone devenv service each, usable on their own and knowing nothing about each other. Prometheus is devenv's ownservices.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.