thermograph/README.md
Emi Griffith 46edb0bca0 Fleet log aggregation: Grafana + Loki + Alloy over the WireGuard mesh
A central Grafana + Loki stack (on beta) fed by a Grafana Alloy agent on
every node, replacing the old SSH-tailed single-host scripts/dashboard.py.

- docker-compose.yml: central Loki (mesh-only :3100) + Grafana (Caddy-fronted)
- loki/config.yml: single-binary, filesystem storage, 30-day retention
- alloy/config.alloy + docker-compose.agent.yml: per-node collector — every
  container's stdout/stderr via the Docker socket, Caddy host logs, and the
  app's structured JSON logs (errors/access/audit), each line tagged by node
- grafana/: auto-provisioned Loki datasource + a fleet-logs dashboard
  (volume by service, error rate, upstream 429s, Caddy 5xx, notifier liveness,
  live tail), with a per-node selector
- caddy-grafana.conf, README, .env.example
2026-07-21 09:11:52 -07:00

4.4 KiB

Thermograph — observability

Fleet-wide log aggregation for Thermograph: a central Grafana + Loki stack, fed by a Grafana Alloy agent on every node, all over the private WireGuard mesh. This replaces the old in-repo scripts/dashboard.py (an SSH-tailed, single-host, metrics-endpoint view) with a persistent, queryable, fleet-wide log store and UI.

  prod (10.10.0.1)          beta (10.10.0.2)            desktop / LAN dev (10.10.0.3)
  ┌────────────┐            ┌──────────────────┐        ┌────────────┐
  │ Alloy agent│──wg0──┐    │ Loki  ◀── Alloy  │   ┌──wg0│ Alloy agent│
  └────────────┘       └───▶│ Grafana (Caddy)  │◀──┘    └────────────┘
   docker+caddy+app         └──────────────────┘         docker+caddy+app
     logs                    central store + UI            logs
  • Loki stores logs (filesystem, 30-day retention). Listens on the mesh IP 10.10.0.2:3100 — never public.
  • Grafana is the UI, fronted by beta's Caddy at grafana.thermograph.org (its own login; Loki is not exposed).
  • Alloy runs on each node and ships three sources to Loki: every Docker container's stdout/stderr, Caddy's host access logs, and the app's structured JSON logs (errors/access/audit *.jsonl, parsed so level/tag/phase become labels). Every line is tagged with its node (host = prod|beta|dev).

Layout

Path What
docker-compose.yml the central Loki + Grafana stack (runs on beta)
loki/config.yml Loki config (filesystem, retention)
grafana/provisioning/ auto-wired Loki datasource + dashboard provider
grafana/dashboards/thermograph-logs.json the fleet-logs dashboard
alloy/config.alloy the per-node collector config
alloy/docker-compose.agent.yml runs the Alloy agent on a node
caddy-grafana.conf beta Caddy site block for the Grafana UI

Deploy

1. Central stack (on beta)

# on beta (75.119.132.91):
git clone <this repo> thermograph-observability && cd thermograph-observability
cp .env.example .env      # set GF_SECURITY_ADMIN_PASSWORD
docker compose up -d      # Loki on 10.10.0.2:3100 + 127.0.0.1:3100; Grafana on 127.0.0.1:3000

Expose the Grafana UI: point grafana.thermograph.org at beta, append caddy-grafana.conf to /etc/caddy/Caddyfile, systemctl reload caddy.

2. Alloy agent (on every node — prod, beta, desktop)

cd thermograph-observability/alloy
# beta pushes to local Loki; prod & desktop push over the mesh:
#   beta:    LOKI_URL=http://127.0.0.1:3100/loki/api/v1/push  ALLOY_NODE=beta
#   prod:    LOKI_URL=http://10.10.0.2:3100/loki/api/v1/push   ALLOY_NODE=prod
#   desktop: LOKI_URL=http://10.10.0.2:3100/loki/api/v1/push   ALLOY_NODE=dev
ALLOY_NODE=prod LOKI_URL=http://10.10.0.2:3100/loki/api/v1/push \
  docker compose -f docker-compose.agent.yml up -d

On the LAN dev node the app's log volume is thermograph-dev_applogs (not thermograph_applogs) — edit the two references in docker-compose.agent.yml there. The mesh must already be up (see the main repo's deploy/swarm/ / INFRA.md); Swarm is not required — the agents talk plain HTTP over wg0.

Verify

# From any mesh node, confirm Loki is receiving from every host:
curl -s 'http://10.10.0.2:3100/loki/api/v1/label/host/values'   # -> {"data":["beta","dev","prod"]}
# Then open Grafana → the "Thermograph — Fleet Logs" dashboard.

The dashboard

Thermograph — Fleet Logs (auto-provisioned), with a Node selector: log volume by service, app error rate by tag, upstream 429 count, Caddy 5xx count, a notifier-liveness log panel, recent app errors, and a live all-container tail. Edit it in Grafana and re-export the JSON here to version a change.

Notes / follow-ups

  • Loki is mesh-only by design; only Grafana (with auth) is public.
  • Alloy reads the Docker socket read-only to discover containers.
  • Retention is 30 days on beta's disk — bump loki/config.yml if you want more.
  • Possible next step: have the app emit a periodic notifier heartbeat log line so notifier liveness is a first-class log-derived signal, not just container chatter.