Files
threadnet-operating/monitoring/docker-compose.yml
T
Thore Cimbal c7aaf388f5 monitoring: the same situation has three different numbers
Grafana 13.2.0 shows 162 findings, 63 series, or 28 vulnerabilities depending on
what is being counted, because the same go stdlib repeats across thirteen plugin
binaries. Prometheus counts series — one per CVE, package and version per target
— so on the dashboard the jump reads 70 to 63, which is fewer, not more.

That makes my original rejection wrong twice over: I compared images with
different contents, and I compared a series count against an occurrence count.
The comment above the version line now carries all three numbers so the next
person does not repeat it, and the README says what a series actually is.
2026-08-21 12:00:00 +00:00

277 lines
11 KiB
YAML

services:
prometheus:
image: prom/prometheus:v3.14.0
container_name: prometheus
restart: unless-stopped
volumes:
# Verzeichnis- statt Einzeldatei-Mount: Docker haengt Einzeldatei-Mounts am
# Inode auf, und `git pull` ersetzt Dateien per Rename (neuer Inode) - der
# Container sah die Aenderung dann nie, waehrend SIGHUP brav Erfolg meldete.
# Verzeichnis-Mounts loesen bei jedem Zugriff ueber den Pfad auf.
# Config-Pfade bleiben unveraendert (--config.file zeigt weiter dorthin).
- ./prometheus:/etc/prometheus:ro
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=45d'
- '--storage.tsdb.retention.size=15GB'
# Remote-Write-Receiver: k3s-Cluster und Matrix-Server pushen hierher.
# ⚠️ Prometheus hat KEINE Authentisierung. Bis 2026-08-21 stand hier
# "9090:9090", also 0.0.0.0 - oeffentlich erreichbar, abgesichert allein
# durch die Hetzner-Firewall. Seither an Adressen gebunden: der private
# vSwitch fuer die pushenden Absender, localhost fuer den Host selbst.
# Wer das zurueckdreht, macht die Zeitreihen wieder oeffentlich lesbar
# UND beschreibbar.
- '--web.enable-remote-write-receiver'
ports:
- "10.0.0.3:9090:9090"
- "127.0.0.1:9090:9090"
networks:
- traefik
alertmanager:
image: prom/alertmanager:v0.34.0
container_name: alertmanager
restart: unless-stopped
volumes:
# Verzeichnis-Mount, gleicher Grund wie bei Prometheus. Die beiden .py aus
# diesem Ordner liegen dadurch mit unter /etc/alertmanager - ungenutzt und
# harmlos, alertmanager laedt ausschliesslich --config.file.
- ./alertmanager:/etc/alertmanager:ro
- alertmanager_data:/alertmanager
command:
- '--config.file=/etc/alertmanager/alertmanager.yml'
- '--storage.path=/alertmanager'
# bewusst kein oeffentlicher Port - nur Prometheus/Receiver im traefik-Netz
networks:
- traefik
# Alertmanager-Webhook -> Matrix (wartung-Raum), gleiche Machart wie
# maintenance-notify (gitops Issue #24). Secrets kommen aus .env.
matrix-alerts:
# 3.13-slim trug 4 CRITICAL (Debian-Basis, ohne Fix in der Fassung);
# 3.13-alpine gemessen am 2026-08-21: 0 CRITICAL, 2 HIGH. Alle drei
# Dienste hier laufen rein auf der stdlib, TLS gegen /etc/ssl/cert.pem
# im Image geprueft - kein Paket, das musl stoeren koennte.
image: python:3.13-alpine
container_name: matrix-alerts
restart: unless-stopped
environment:
- MATRIX_HOMESERVER=${MATRIX_ALERT_HOMESERVER}
- MATRIX_ROOM_ID=${MATRIX_ALERT_ROOM_ID}
- MATRIX_TOKEN=${MATRIX_ALERT_TOKEN}
# Raum-Routing (gitops#47): Alerts mit Label room=security -> Security-Raum
- MATRIX_ROOM_SECURITY=${MATRIX_RELEASE_ROOM_ID:-}
# Ohne das liegt der State unter /tmp im Writable Layer: der ueberlebt
# zwar ein "compose restart", aber kein "up -d", das den Container neu
# baut -- also genau jeden Deploy. Dann verlieren offene Alarme ihre
# Event-Zuordnung und loesen sich ueber den Fallback als separate
# Nachricht auf, statt die Firing-Nachricht abzuhaken.
- MATRIX_STATE_FILE=/state/matrix-alerts-state.json
volumes:
- ./alertmanager:/app:ro
- matrix_alerts_data:/state
command: python3 /app/matrix-alerts.py
networks:
- traefik
# Release-/Advisory-Watch (gitops#22): meldet neue Releases der Element-Stack-
# Upstreams in den Alerts-Raum (🚨 bei Security-Verdacht). Gleicher Bot/Raum
# wie matrix-alerts, eigener State (Erstlauf merkt nur, flutet nicht).
release-watch:
# 3.13-slim trug 4 CRITICAL (Debian-Basis, ohne Fix in der Fassung);
# 3.13-alpine gemessen am 2026-08-21: 0 CRITICAL, 2 HIGH. Alle drei
# Dienste hier laufen rein auf der stdlib, TLS gegen /etc/ssl/cert.pem
# im Image geprueft - kein Paket, das musl stoeren koennte.
image: python:3.13-alpine
container_name: release-watch
restart: unless-stopped
environment:
- MATRIX_HOMESERVER=${MATRIX_ALERT_HOMESERVER}
- MATRIX_ROOM_ID=${MATRIX_ALERT_ROOM_ID}
# CVE-/Release-Raum (gitops#47): leer lassen = Fallback Alerts-Raum
- MATRIX_RELEASE_ROOM_ID=${MATRIX_RELEASE_ROOM_ID:-}
- MATRIX_TOKEN=${MATRIX_ALERT_TOKEN}
volumes:
- ./alertmanager:/app:ro
- release_watch_data:/state
command: python3 /app/release-watch.py
networks:
- traefik
# CVE-Pipeline (gitops#47), Teil 1: Trivy scannt die real deployten Images
# (Ziele aus /targets/targets.txt - abgeleitet vom cve-exporter, NICHT von Hand
# pflegen: #0106 / ADR-0026)
cve-scan:
image: aquasec/trivy:0.74.0
container_name: cve-scan
restart: unless-stopped
entrypoint: ["/bin/sh", "/config/scan-loop.sh"]
volumes:
- ./cve:/config:ro
- cve_results:/results
- cve_trivy_cache:/root/.cache
# Ab Slice 3 die Quelle der Ziele; in Slice 1 nur eingehaengt, damit
# der Pfad existiert und beim Umschalten kein Rennen entsteht.
- cve_targets:/targets:ro
networks:
- traefik
# Teil 2: Reports -> Prometheus-Metriken (Schema + Pflichtfelder siehe gitops#47)
cve-exporter:
# 3.13-slim trug 4 CRITICAL (Debian-Basis, ohne Fix in der Fassung);
# 3.13-alpine gemessen am 2026-08-21: 0 CRITICAL, 2 HIGH. Alle drei
# Dienste hier laufen rein auf der stdlib, TLS gegen /etc/ssl/cert.pem
# im Image geprueft - kein Paket, das musl stoeren koennte.
image: python:3.13-alpine
container_name: cve-exporter
restart: unless-stopped
environment:
- RESULTS_DIR=/results
- STATE_FILE=/state/first-seen.json
# Soll-Menge der Scan-Ziele (#0106, ADR-0026): abgeleitet, nicht gepflegt.
# Der Exporter bildet sie, weil er fuer die Deckungsmetrik ohnehin BEIDE
# Mengen kennen muss - ein zweiter Ort waere eine zweite Wahrheit.
- TARGETS_FILE=/targets/targets.txt
- PROMETHEUS_URL=http://prometheus:9090
- REGISTRY_HOST=rohana.axion1337.de
# Reichweite (Entscheidung sorb 2026-08-21, Option C): Bestand plus die
# letzten drei Fassungen je Repo - so weit, wie ein Rollback zielt.
- REGISTRY_TAGS_JE_REPO=3
# Nicht je Scrape herleiten: bei 15 s waeren das ~21 000
# Registry-Anfragen am Tag; eine Runde kostet ~90.
- SOLL_INTERVALL_SEKUNDEN=3600
# Entschiedene CRITICAL-Befunde (#0051). Liegt im selben ./cve-Ordner,
# der hier als /app eingehaengt ist - ausdruecklich gesetzt, damit man
# die Datei findet, ohne den Vorgabewert im Quelltext zu suchen.
- ENTSCHEIDUNGEN_FILE=/app/entscheidungen.json
volumes:
- ./cve:/app:ro
- cve_results:/results:ro
- cve_exporter_state:/state
- cve_targets:/targets
command: python3 /app/cve-exporter.py
networks:
- traefik
loki:
# 3.7.1 -> 3.7.6, gemessen: 41 HIGH -> 8, keine CRITICAL in beiden.
image: grafana/loki:3.7.6
container_name: loki
restart: unless-stopped
volumes:
- ./loki:/etc/loki:ro
- loki_data:/loki
command: -config.file=/etc/loki/loki-config.yaml
# ⚠️ Externe Alloys pushen Logs hierher; Loki hat ebenfalls keine
# Authentisierung. Bis 2026-08-21 auf 0.0.0.0 gebunden, seither wie
# Prometheus: privater vSwitch fuer die Absender, localhost fuer den Host.
ports:
- "10.0.0.3:3100:3100"
- "127.0.0.1:3100:3100"
networks:
- traefik
alloy:
# v1.16.0 -> v1.18.1, gemessen: 44 HIGH -> 14. v1.19.0 gibt es nur als
# Vorabfassung; im Cluster laeuft alloy aus dem Chart, nicht von hier.
image: grafana/alloy:v1.18.1
container_name: alloy
restart: unless-stopped
volumes:
- ./alloy:/etc/alloy:ro
- /var/log:/var/log:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
# Muss persistent sein, sonst ist die Positions-Datei nach jedem
# Recreate weg und Alloy liest alle Container-Logs von vorn. Loki
# weist die alten Eintraege dann ab (reject_old_samples, Default 7d).
- alloy_data:/var/lib/alloy
command: run --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy
cap_add:
- DAC_READ_SEARCH
- NET_RAW
networks:
- traefik
depends_on:
- prometheus
- loki
grafana:
# ⚠️ NICHT auf die 12er-Reihe zurueckdrehen, weil irgendwo eine groessere
# HIGH-Zahl steht. Ab 13.x liegen 13 Datenquellen-Plugins IM Image (12.4.9
# hatte keine): aus 5 Scan-Zielen werden 16, und die Befunde sitzen
# saemtlich in diesen Plugin-Binaries - opentsdb, jaeger, stackdriver,
# tempo, cloudmonitoring, also Datenquellen, die wir nicht benutzen.
# Der KERN von 13.2.0 traegt 0 CRITICAL und 0 HIGH; 12.4.9 trug dort 1
# und 3.
#
# ⚠️ UND: dieselbe Lage hat drei verschiedene Zahlen, je nachdem, was man
# zaehlt - 162 Vorkommen, 63 Prometheus-Serien, 28 verschiedene CVEs.
# Dieselbe Go-stdlib wiederholt sich ueber 13 Binaries. Auf dem Dashboard
# (das Serien zaehlt) steht der Sprung als 70 -> 63, also WENIGER.
# Wer hier vergleicht, muss dieselbe Zaehlweise auf beiden Seiten nehmen.
image: grafana/grafana:13.2.0
container_name: grafana
restart: unless-stopped
environment:
# Greift nur beim allerersten Start mit leerem Volume.
# Der Live-Admin ist davon unabhaengig (Passwortaenderungen in der UI).
- GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER}
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik"
- "traefik.http.routers.grafana.rule=Host(`selendis.axion1337.de`)"
- "traefik.http.routers.grafana.entrypoints=websecure"
- "traefik.http.routers.grafana.tls.certresolver=letsencrypt"
- "traefik.http.services.grafana.loadbalancer.server.port=3000"
depends_on:
- prometheus
- loki
networks:
- traefik
node-exporter:
image: prom/node-exporter:v1.12.1
container_name: node-exporter
restart: unless-stopped
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--path.rootfs=/rootfs'
- '--web.listen-address=:9100'
- '--web.disable-exporter-metrics'
# Nur localhost: gescrapt wird aus demselben Host heraus, von aussen hat
# hier niemand etwas zu suchen. Bis 2026-08-21 stand auch das auf 0.0.0.0.
ports:
- "127.0.0.1:9100:9100"
networks:
- traefik
volumes:
prometheus_data:
alertmanager_data:
matrix_alerts_data:
release_watch_data:
cve_results:
cve_trivy_cache:
cve_exporter_state:
cve_targets:
grafana_data:
loki_data:
alloy_data:
networks:
traefik:
external: true