Files
threadnet-operating/monitoring/docker-compose.yml
T
Thore Cimbal 516641bbba monitoring: five image versions, measured before choosing (#0051)
The operating stack carried 30 critical and 676 high findings. These five lines
remove fourteen and 278 of them. Every target was scanned before it was written
into the file:

  trivy          0.58.2  -> 0.74.0    critical 4 -> 0, high 98 -> 0
  grafana        12.0.0  -> 12.4.9    critical 7 -> 1, high 70 -> 3
  prometheus     v3.3.1  -> v3.14.0   critical 2 -> 0, high 46 -> 2
  alertmanager   v0.28.1 -> v0.34.0   critical 1 -> 0, high 41 -> 2
  node-exporter  v1.9.1  -> v1.12.1   critical 1 -> 0, high 38 -> 8

Three candidates were measured and rejected, which is the point of measuring.
Grafana 13.2.0 clears all seven critical findings but takes high from 70 to 162,
so the minor jump inside 12.x beats the major one by a wide margin. cadvisor only
goes from five critical to four. And python:3.13-slim is unchanged — the host
already holds the current build, so a repull buys nothing.

Configuration was validated against the new tools rather than the old ones:
promtool v3.14.0 accepts prometheus.yml with both rule files, all 20 plus 5
rules, and the rule unit tests; amtool v0.34.0 accepts alertmanager.yml.

⚠️ Needs a deploy on the operating host. Recreate rather than up: the images
change, and the CVE targets themselves move with trivy and the exporter.
2026-08-21 12:00:00 +00:00

236 lines
8.2 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.
# ACHTUNG: Port 9090 ist oeffentlich und ohne Auth erreichbar --
# per Hetzner Cloud Firewall auf die Absender-IPs einschraenken!
- '--web.enable-remote-write-receiver'
ports:
- "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:
image: python:3.13-slim
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:
image: python:3.13-slim
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:
image: python:3.13-slim
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
volumes:
- ./cve:/app:ro
- cve_results:/results:ro
- cve_exporter_state:/state
- cve_targets:/targets
command: python3 /app/cve-exporter.py
networks:
- traefik
loki:
image: grafana/loki:3.7.1
container_name: loki
restart: unless-stopped
volumes:
- ./loki:/etc/loki:ro
- loki_data:/loki
command: -config.file=/etc/loki/loki-config.yaml
# Port 3100 oeffentlich: externe Alloys pushen Logs hierher.
# Ebenfalls per Firewall auf bekannte Absender-IPs einschraenken.
ports:
- "3100:3100"
networks:
- traefik
alloy:
image: grafana/alloy:v1.16.0
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:
image: grafana/grafana:12.4.9
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'
ports:
- "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