Part of #5365.
Context: The self-host stack currently has zero visibility into host-level resource usage, per-container resource usage, or Redis — despite Redis being an always-on, required backend. Meanwhile two "free wins" exist: Qdrant already exposes a native Prometheus /metrics endpoint (:6333/metrics) that's simply never scraped, and Prometheus/Alertmanager/Loki/Tempo/Grafana all expose their own native /metrics too, unscraped — so the observability stack itself is currently unmonitored.
Requirements:
- Add
node-exporter (prom/node-exporter, official image) — host CPU/memory/disk/network. Standard --profile observability-gated service, mirroring the existing gpu-exporter/postgres-exporter wiring conventions in docker-compose.yml (health-gated, resource-limited via deploy.resources.limits.memory with an .env.example-documented override var, per the pattern this repo already uses for every other exporter).
- Add
cadvisor (gcr.io/cadvisor/cadvisor, official image) — per-container CPU/memory/network/disk-io. Needs read-only mounts of /var/run/docker.sock, /sys, /var/lib/docker — evaluate whether this should go through the existing isolated docker-proxy network (the same isolation reasoning already documented for Promtail's container-discovery access) rather than mounting the raw socket directly.
- Add
redis_exporter (oliver006/redis_exporter, official image) — Redis has no native Prometheus endpoint; this is the standard sidecar.
- Add a
qdrant scrape target to prometheus/prometheus.yml (qdrant:6333, path /metrics) — no new service needed, already confirmed live.
- Add self-monitoring scrape targets for
prometheus:9090, alertmanager:9093, loki:3100, tempo:3200, grafana:3000 (/metrics on each, confirmed live against edge-nl-01) — mirror the existing "absent target just shows down, no alert fires" comment convention already used for GPU/backup targets, since not every self-hoster runs every one of these (e.g., Qdrant is --profile qdrant-gated).
- Enable the OTEL collector's internal telemetry (
service.telemetry.metrics.address: 0.0.0.0:8888 in otel/otel-collector-config.yml) and add a matching scrape target — currently only logs.level: warn is configured, so the collector's own health/dropped-span/dropped-metric counters are invisible.
- Build a new Grafana dashboard (e.g.
grafana/dashboards/infra-health.json) visualizing all of the above: host CPU/mem/disk/network, per-container resource usage (top-N by CPU/memory), Redis (connected clients, memory, ops/sec, hit ratio), Qdrant (collections, vector count, per-collection breakdown from the metrics already confirmed available), and a "stack self-health" row (Prometheus/Alertmanager/Loki/Tempo/Grafana/OTEL-collector all show as scraped/up). Add it to resource-hub.json's dashboard catalog.
- Add
deploy.resources.limits.memory for all 3 new services, extend test/unit/selfhost-compose-resource-limits.test.ts and .env.example to match the established pattern.
- Extend
node scripts/validate-observability-configs.mjs-covered files as needed; add/extend dashboard tests mirroring test/unit/selfhost-grafana-dashboard.test.ts's conventions (dangling-metric-ref checks, panel-id uniqueness, etc.).
Deliverables:
- 3 new
docker-compose.yml services (node-exporter, cadvisor, redis_exporter), each --profile observability-gated, resource-limited, documented in .env.example.
- Updated
prometheus/prometheus.yml with 6 new/updated scrape targets (qdrant, prometheus, alertmanager, loki, tempo, grafana) plus the otel-collector self-telemetry target.
- Updated
otel/otel-collector-config.yml enabling internal telemetry.
- New
grafana/dashboards/infra-health.json (or equivalent), linked from resource-hub.json.
- Full local-gate-passing test coverage for every change above.
Expected outcomes: A self-hoster can see, from Grafana alone: whether the host machine itself is under memory/CPU/disk pressure; which container is the actual resource hog when something looks off; Redis health without shelling in; Qdrant collection/vector growth over time; and whether the observability stack itself (Prometheus/Loki/Tempo/Grafana/Alertmanager/OTEL collector) is healthy and actually ingesting data, not silently broken.
Links/resources:
- Existing precedent to mirror:
gpu-exporter/postgres-exporter/backup-exporter service definitions in docker-compose.yml, and grafana/dashboards/gpu-metrics.json as the template for a focused infra dashboard.
prom/node-exporter, gcr.io/cadvisor/cadvisor, oliver006/redis_exporter — all official, widely-used images.
- Qdrant's native metrics confirmed live:
curl http://qdrant:6333/metrics returns real Prometheus text (app_info, collections_total, collections_vector_total, etc.) as of 2026-07-12.
Part of #5365.
Context: The self-host stack currently has zero visibility into host-level resource usage, per-container resource usage, or Redis — despite Redis being an always-on, required backend. Meanwhile two "free wins" exist: Qdrant already exposes a native Prometheus
/metricsendpoint (:6333/metrics) that's simply never scraped, and Prometheus/Alertmanager/Loki/Tempo/Grafana all expose their own native/metricstoo, unscraped — so the observability stack itself is currently unmonitored.Requirements:
node-exporter(prom/node-exporter, official image) — host CPU/memory/disk/network. Standard--profile observability-gated service, mirroring the existinggpu-exporter/postgres-exporterwiring conventions indocker-compose.yml(health-gated, resource-limited viadeploy.resources.limits.memorywith an.env.example-documented override var, per the pattern this repo already uses for every other exporter).cadvisor(gcr.io/cadvisor/cadvisor, official image) — per-container CPU/memory/network/disk-io. Needs read-only mounts of/var/run/docker.sock,/sys,/var/lib/docker— evaluate whether this should go through the existing isolateddocker-proxynetwork (the same isolation reasoning already documented for Promtail's container-discovery access) rather than mounting the raw socket directly.redis_exporter(oliver006/redis_exporter, official image) — Redis has no native Prometheus endpoint; this is the standard sidecar.qdrantscrape target toprometheus/prometheus.yml(qdrant:6333, path/metrics) — no new service needed, already confirmed live.prometheus:9090,alertmanager:9093,loki:3100,tempo:3200,grafana:3000(/metricson each, confirmed live against edge-nl-01) — mirror the existing "absent target just shows down, no alert fires" comment convention already used for GPU/backup targets, since not every self-hoster runs every one of these (e.g., Qdrant is--profile qdrant-gated).service.telemetry.metrics.address: 0.0.0.0:8888inotel/otel-collector-config.yml) and add a matching scrape target — currently onlylogs.level: warnis configured, so the collector's own health/dropped-span/dropped-metric counters are invisible.grafana/dashboards/infra-health.json) visualizing all of the above: host CPU/mem/disk/network, per-container resource usage (top-N by CPU/memory), Redis (connected clients, memory, ops/sec, hit ratio), Qdrant (collections, vector count, per-collection breakdown from the metrics already confirmed available), and a "stack self-health" row (Prometheus/Alertmanager/Loki/Tempo/Grafana/OTEL-collector all show as scraped/up). Add it toresource-hub.json's dashboard catalog.deploy.resources.limits.memoryfor all 3 new services, extendtest/unit/selfhost-compose-resource-limits.test.tsand.env.exampleto match the established pattern.node scripts/validate-observability-configs.mjs-covered files as needed; add/extend dashboard tests mirroringtest/unit/selfhost-grafana-dashboard.test.ts's conventions (dangling-metric-ref checks, panel-id uniqueness, etc.).Deliverables:
docker-compose.ymlservices (node-exporter,cadvisor,redis_exporter), each--profile observability-gated, resource-limited, documented in.env.example.prometheus/prometheus.ymlwith 6 new/updated scrape targets (qdrant, prometheus, alertmanager, loki, tempo, grafana) plus the otel-collector self-telemetry target.otel/otel-collector-config.ymlenabling internal telemetry.grafana/dashboards/infra-health.json(or equivalent), linked fromresource-hub.json.Expected outcomes: A self-hoster can see, from Grafana alone: whether the host machine itself is under memory/CPU/disk pressure; which container is the actual resource hog when something looks off; Redis health without shelling in; Qdrant collection/vector growth over time; and whether the observability stack itself (Prometheus/Loki/Tempo/Grafana/Alertmanager/OTEL collector) is healthy and actually ingesting data, not silently broken.
Links/resources:
gpu-exporter/postgres-exporter/backup-exporterservice definitions indocker-compose.yml, andgrafana/dashboards/gpu-metrics.jsonas the template for a focused infra dashboard.prom/node-exporter,gcr.io/cadvisor/cadvisor,oliver006/redis_exporter— all official, widely-used images.curl http://qdrant:6333/metricsreturns real Prometheus text (app_info,collections_total,collections_vector_total, etc.) as of 2026-07-12.