Log-Rotation, Ressourcen-Limits und Healthchecks für den Dauerbetrieb
Docker-Container laufen im Test meist stabil, doch im Dauerbetrieb entscheiden Log-Rotation, die richtige Storage-Driver-Wahl, saubere systemd-Integration und klare Ressourcen-Limits darüber, ob ein einzelner Container den gesamten Server lahmlegt oder stabil im Hintergrund läuft. Dieser Artikel zeigt, wie Administratoren den Docker-Daemon produktionstauglich konfigurieren, Container zuverlässig automatisch starten lassen und ihren Gesundheitszustand kontinuierlich überwachen.
Inhaltsverzeichnis
- 1. Warum Docker im Dauerbetrieb andere Regeln braucht
- 2. Docker-Daemon-Konfiguration: Log-Rotation und Storage-Driver
- 3. systemd-Integration: Zuverlässiger Start und Neustart
- 4. Ressourcen-Limits pro Container: CPU, Memory und PIDs
- 5. Docker Compose in Produktion: restart-Policies und Deploy-Limits
- 6. Monitoring und Health-Checks: Containerzustand überwachen
- 7. Netzwerk- und Volume-Strategien für Persistenz und Isolation
- 8. Sicherheit: Non-Root-Container, Capabilities und Secrets
- 9. Docker-Konfigurationen im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Docker im Dauerbetrieb andere Regeln braucht
In der lokalen Entwicklungsumgebung startet ein Container mit docker run und läuft meist genau so lange, wie er gebraucht wird. Im Dauerbetrieb auf einem Linux-Server gelten andere Regeln: Container laufen Wochen oder Monate, Logdateien wachsen unbegrenzt weiter, und ein Neustart des Servers darf nicht dazu führen, dass wichtige Dienste manuell wieder hochgefahren werden müssen. Wer Docker produktiv betreibt, muss den Daemon, die Container-Konfiguration und die Systemintegration so aufsetzen, dass der Betrieb auch ohne ständiges manuelles Eingreifen stabil bleibt.
Drei Problemfelder tauchen in der Praxis am häufigsten auf: unkontrolliertes Log-Wachstum, das die Festplatte füllt, ein einzelner Container ohne Ressourcen-Limit, der den gesamten Host durch Speicher- oder CPU-Verbrauch lahmlegt, und Container, die nach einem Reboot nicht automatisch wieder starten. Die folgenden Abschnitte behandeln systematisch, wie sich Docker-Daemon, systemd-Integration, Ressourcen-Limits und Monitoring konfigurieren lassen, damit ein Linux-Server mit vielen Containern zuverlässig im Dauerbetrieb läuft.
2. Docker-Daemon-Konfiguration: Log-Rotation und Storage-Driver
Die Standardkonfiguration des Docker-Daemons ist für schnelles Ausprobieren gedacht, nicht für den Produktionsbetrieb. Ohne explizite Log-Konfiguration verwendet Docker den Log-Treiber json-file ohne Größenbegrenzung: Jede Ausgabe eines Containers landet unbegrenzt wachsend unter /var/lib/docker/containers/<id>/<id>-json.log. Bei einem Container, der viel protokolliert, etwa ein Webserver mit Access-Log auf stdout, kann diese Datei innerhalb weniger Wochen mehrere Gigabyte erreichen und im schlimmsten Fall die gesamte Root-Partition füllen. Die Lösung liegt in der Datei /etc/docker/daemon.json, in der sich max-size und max-file für den Log-Treiber global festlegen lassen.
Genauso wichtig ist die Wahl des Storage-Drivers. Seit Docker 20.10 ist overlay2 der empfohlene Standard auf allen modernen Linux-Kerneln mit ext4 oder xfs, während der ältere devicemapper-Treiber im Loopback-Modus für Produktionsumgebungen ausdrücklich ungeeignet ist und in aktuellen Docker-Versionen nicht mehr unterstützt wird. Nach jeder Änderung an daemon.json muss der Daemon mit systemctl restart docker neu gestartet werden, was normalerweise alle laufenden Container beendet, sofern live-restore nicht aktiviert ist. Mit live-restore: true bleiben Container beim Daemon-Neustart am Laufen, was Wartungsfenster für Docker-Updates erheblich verkürzt.
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3",
"compress": "true"
},
"storage-driver": "overlay2",
"live-restore": true,
"default-address-pools": [
{ "base": "172.30.0.0/16", "size": 24 }
],
"userland-proxy": false
}
3. systemd-Integration: Zuverlässiger Start und Neustart
Der Docker-Daemon selbst läuft als systemd-Service (docker.service) und wird mit systemctl enable docker beim Booten automatisch gestartet. Das allein reicht jedoch nicht aus, um einzelne Container nach einem Reboot wieder verfügbar zu machen. Container, die mit docker run ohne Restart-Policy gestartet wurden, bleiben nach einem Neustart des Servers dauerhaft gestoppt, weil Docker sich nur dann an den zuletzt gewünschten Zustand erinnert, wenn eine Policy wie --restart unless-stopped oder --restart always gesetzt ist. Für Einzelcontainer ist die Restart-Policy also die einfachste Form der systemd-Integration, weil der Docker-Daemon selbst beim Systemstart alle Container mit passender Policy automatisch hochfährt.
Für komplexere Setups mit mehreren zusammengehörigen Containern empfiehlt sich ein eigenes systemd-Unit-File, das docker compose up und docker compose down kapselt und explizit von docker.service abhängt. Damit lässt sich der Start eines gesamten Compose-Stacks in die reguläre Boot-Reihenfolge einordnen, inklusive After=network-online.target, damit der Stack erst startet, wenn das Netzwerk tatsächlich verfügbar ist. Das verhindert Startfehler bei Containern, die beim Hochfahren sofort eine externe Datenbank oder einen DNS-Namen erreichen müssen, aber noch keine funktionierende Netzwerkverbindung haben.
# /etc/systemd/system/docker-compose-app.service
[Unit]
Description=Application Docker Compose Stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/app
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=120
[Install]
WantedBy=multi-user.target
4. Ressourcen-Limits pro Container: CPU, Memory und PIDs
Ohne explizite Limits kann ein einzelner Container theoretisch den gesamten Arbeitsspeicher und alle CPU-Kerne des Hosts beanspruchen. Das ist besonders auf Servern mit mehreren Diensten riskant: Ein fehlerhaft konfigurierter Container mit einem Memory-Leak kann den Out-Of-Memory-Killer des Kernels auslösen, der dann nicht zwingend den fehlerhaften Prozess beendet, sondern irgendeinen Prozess auf dem System, im schlimmsten Fall die Datenbank oder den SSH-Daemon. Die Flags --memory und --memory-swap bei docker run, beziehungsweise die Felder mem_limit oder deploy.resources.limits in Compose, setzen eine harte Obergrenze, bei deren Überschreitung ausschließlich der jeweilige Container vom OOM-Killer beendet wird, nicht der gesamte Host.
Genauso wichtig ist die Begrenzung der CPU-Zeit über --cpus, das über cgroups die maximale Anzahl an CPU-Kernen anteilig zuweist, sowie --pids-limit, das die Anzahl gleichzeitiger Prozesse und Threads innerhalb eines Containers begrenzt und damit sogenannte Fork-Bomben wirkungsvoll verhindert. Aktuelle Docker-Installationen nutzen cgroups v2, prüfbar mit docker info | grep -i cgroup. Der Befehl docker stats zeigt live, wie viel CPU, Speicher und Netzwerk jeder Container tatsächlich verbraucht, und ist der erste Anlaufpunkt bei der Diagnose, welcher Container einen Server ausbremst.
#!/usr/bin/env bash
# Hard limits for a single container: memory, swap, CPU shares and PIDs
docker run -d \
--name magento-worker \
--memory="1g" \
--memory-swap="1g" \
--cpus="1.5" \
--pids-limit=200 \
--restart unless-stopped \
registry.mironsoft.de/magento-worker:latest
# Live resource usage across all running containers
docker stats --no-stream --format \
"table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.PIDs}}"
# Check whether the host runs cgroups v2 (required for --pids-limit accuracy)
docker info | grep -i cgroup
5. Docker Compose in Produktion: restart-Policies und Deploy-Limits
In Docker Compose lassen sich Ressourcen-Limits und Restart-Verhalten deklarativ im YAML festhalten, statt sie bei jedem docker run erneut als Flags mitzugeben. Der Schlüssel deploy.resources.limits definiert die Obergrenze für CPU und Speicher, deploy.resources.reservations legt fest, welche Ressourcen mindestens garantiert sind, was insbesondere auf Servern mit knappem Speicher für vorhersehbares Verhalten sorgt. Die Restart-Policy unless-stopped ist für die meisten produktiven Dienste die richtige Wahl, weil sie einen Container nach jedem Absturz und nach jedem Server-Reboot automatisch neu startet, ihn aber nicht erneut hochfährt, wenn er ausdrücklich mit docker stop beendet wurde.
Der healthcheck-Block in Compose ergänzt die reine Prozessüberwachung um eine anwendungsspezifische Prüfung: Ein Container kann laufen, ohne dass der dahinterliegende Dienst tatsächlich Anfragen beantwortet, etwa während einer langen Datenbankmigration beim Start. Mit test, interval, timeout und retries lässt sich definieren, wann ein Container als healthy gilt, und abhängige Services können über depends_on: condition: service_healthy erst starten, wenn diese Prüfung erfolgreich war. Das verhindert Race-Conditions, bei denen eine Anwendung startet, bevor die Datenbank tatsächlich Verbindungen akzeptiert.
# docker-compose.yml: production-ready service definition
services:
app:
image: registry.mironsoft.de/magento-app:2.4.8
restart: unless-stopped
deploy:
resources:
limits:
cpus: "2.0"
memory: 2g
reservations:
cpus: "0.5"
memory: 512m
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 40s
depends_on:
db:
condition: service_healthy
networks:
- app-net
db:
image: mysql:8.0
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- db-data:/var/lib/mysql
networks:
- app-net
volumes:
db-data:
networks:
app-net:
driver: bridge
6. Monitoring und Health-Checks: Containerzustand überwachen
Die HEALTHCHECK-Anweisung im Dockerfile oder im Compose-File definiert, welcher Befehl innerhalb des Containers regelmäßig ausgeführt wird, um den Anwendungszustand zu prüfen, üblicherweise ein einfacher HTTP-Request gegen einen /health-Endpunkt oder ein Datenbank-Ping. Der Status landet direkt im Docker-Objekt und ist mit docker inspect --format='{{.State.Health.Status}}' <container> abrufbar, mit den möglichen Werten starting, healthy und unhealthy. Diese eingebaute Prüfung ist die Grundlage für automatisiertes Monitoring, weil externe Tools nicht selbst wissen müssen, wie ein einzelner Dienst geprüft wird.
Für den produktiven Betrieb reicht ein einzelner Healthcheck aber nicht aus: Ein Container, der als unhealthy markiert wird, startet ohne zusätzliche Automatisierung nicht automatisch neu. Das Zusatztool autoheal beobachtet über den Docker-Socket alle Container mit aktivem Healthcheck und startet ungesunde Container automatisch neu. Für serverweites Monitoring über mehrere Hosts hinweg exportiert cAdvisor detaillierte Container-Metriken im Prometheus-Format, während node_exporter die Host-Ebene abdeckt. Beide zusammen liefern in Grafana ein vollständiges Bild aus Container-Gesundheit, Ressourcenverbrauch und Host-Auslastung.
#!/usr/bin/env bash
# Poll all running containers and alert on unhealthy status
set -euo pipefail
for cid in $(docker ps -q); do
name=$(docker inspect --format='{{.Name}}' "$cid" | sed 's#^/##')
status=$(docker inspect --format='{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$cid")
if [[ "$status" == "unhealthy" ]]; then
echo "[ALERT] Container $name is unhealthy, restarting" >&2
docker restart "$cid"
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"text\":\"Container $name was unhealthy and got restarted\"}"
fi
done
7. Netzwerk- und Volume-Strategien für Persistenz und Isolation
Für persistente Daten sind benannte Volumes (docker volume create beziehungsweise das volumes-Feld in Compose) Bind Mounts in den meisten Fällen vorzuziehen, weil sie von Docker selbst verwaltet werden, plattformunabhängig funktionieren und sich nicht versehentlich mit Host-Berechtigungsproblemen vermischen. Bind Mounts bleiben sinnvoll, wenn Entwickler Quellcode während der Entwicklung live in den Container spiegeln wollen, gehören im Produktivbetrieb aber vor allem dorthin, wo Konfigurationsdateien read-only eingebunden werden. Ein Volume lässt sich einfach über einen temporären Hilfscontainer sichern, der das Volume mountet und den Inhalt per tar in ein Archiv auf dem Host schreibt.
Netzwerkseitig sollte jeder Compose-Stack ein eigenes, benutzerdefiniertes Bridge-Netzwerk erhalten, statt sich das Standardnetzwerk mit allen anderen Containern auf dem Host zu teilen. Benutzerdefinierte Netzwerke bringen automatische DNS-Auflösung zwischen Containern über den Servicenamen mit, sodass ein Anwendungscontainer die Datenbank einfach über db:3306 statt über eine feste IP-Adresse erreicht. Der Modus --network host umgeht die Netzwerkisolation vollständig und sollte nur für sehr spezifische Fälle wie Monitoring-Agenten mit Zugriff auf alle Host-Ports verwendet werden, niemals als Standard für Anwendungscontainer.
8. Sicherheit: Non-Root-Container, Capabilities und Secrets
Container laufen standardmäßig als Root-Benutzer innerhalb ihres Namespaces, was bei einem Ausbruch aus dem Container-Isolationsmechanismus, etwa durch eine Kernel-Schwachstelle, direkten Root-Zugriff auf den Host bedeuten kann. Die Anweisung USER im Dockerfile mit einer nicht-privilegierten UID reduziert dieses Risiko erheblich, ebenso wie das Flag --read-only, das das Root-Dateisystem des Containers unveränderlich macht und nur explizit deklarierte Volumes beschreibbar lässt. Für Anwendungen, die temporäre Dateien schreiben müssen, kombiniert man --read-only mit --tmpfs /tmp, um weiterhin einen beschreibbaren, aber flüchtigen Bereich bereitzustellen.
Die Linux-Capabilities eines Containers lassen sich mit --cap-drop ALL vollständig entfernen und anschließend gezielt mit --cap-add nur für tatsächlich benötigte Funktionen wieder hinzufügen, etwa NET_BIND_SERVICE für Prozesse, die auf Port 80 binden müssen. Das Flag --privileged hebt sämtliche Isolation auf und sollte in Produktionsumgebungen praktisch nie verwendet werden. Für sensible Zugangsdaten wie Datenbankpasswörter sind Docker Secrets oder gemountete Dateien Umgebungsvariablen vorzuziehen, weil Umgebungsvariablen über docker inspect und in Prozesslisten für alle Nutzer mit Docker-Zugriff sichtbar sind, während Secrets nur innerhalb des jeweiligen Containers gemountet werden.
9. Docker-Konfigurationen im direkten Vergleich
Viele Docker-Standardeinstellungen sind für lokale Entwicklung gedacht und im Produktivbetrieb ein Risiko. Die folgende Übersicht zeigt die wichtigsten Unterschiede zwischen einer unsicheren Standardkonfiguration und der empfohlenen produktionstauglichen Einstellung.
| Bereich | Unsicherer Standard | Empfohlene Produktionseinstellung | Vorteil |
|---|---|---|---|
| Log-Treiber | json-file ohne Limit | max-size + max-file in daemon.json | Verhindert volle Root-Partition |
| Storage-Driver | devicemapper (loopback) | overlay2 | Stabiler, offiziell unterstützt |
| Restart-Verhalten | kein --restart gesetzt | --restart unless-stopped | Übersteht Reboot und Crash |
| Ressourcen | keine Limits gesetzt | --memory, --cpus, --pids-limit | Ein Container starvt nicht den Host |
| Benutzer im Container | root (Default) | USER mit UID + --cap-drop ALL | Reduziert Schaden bei Container-Escape |
In der Praxis wirken sich vor allem die ersten beiden Zeilen der Tabelle am stärksten auf die Stabilität aus, weil unbegrenztes Logwachstum und ein fehlendes Ressourcen-Limit die häufigsten Ursachen für einen kompletten Server-Ausfall sind. Wer diese Defaults einmal projektweit korrigiert, etwa zentral in der daemon.json und in einer gemeinsamen Compose-Basis-Datei, muss sie nicht für jeden neuen Container erneut einzeln setzen.
Mironsoft
Docker-Infrastruktur, systemd-Automatisierung und Server-Hardening für produktive Linux-Umgebungen
Docker-Infrastruktur, die auch nachts durchläuft?
Wir analysieren bestehende Docker-Setups, identifizieren fehlende Ressourcen-Limits und unsichere Defaults und richten Daemon-Konfiguration, systemd-Integration und Monitoring so ein, dass eure Container-Infrastruktur zuverlässig im Dauerbetrieb läuft.
Daemon-Audit
Log-Rotation, Storage-Driver und Ressourcen-Limits produktionsreif konfigurieren
systemd-Integration
Zuverlässiger Start, geordnete Boot-Reihenfolge und automatischer Neustart nach Ausfällen
Monitoring-Setup
Healthchecks, cAdvisor und Alerting für Container-Gesundheit einrichten
10. Zusammenfassung
Docker auf einem Linux-Server produktiv betreiben heißt vor allem, die komfortablen Standardeinstellungen für lokale Entwicklung durch explizite, produktionstaugliche Konfiguration zu ersetzen. Log-Rotation über max-size und max-file in der daemon.json verhindert eine volle Root-Partition. Der Storage-Driver overlay2 ist die stabile, offiziell unterstützte Wahl. systemd-Integration über Restart-Policies und eigene Unit-Files sorgt dafür, dass Container und ganze Compose-Stacks nach jedem Reboot zuverlässig wieder hochfahren. Ressourcen-Limits über --memory, --cpus und --pids-limit stellen sicher, dass ein fehlerhafter Container nicht den gesamten Host mit in den Absturz reißt.
Der letzte, oft unterschätzte Baustein ist kontinuierliches Monitoring: Healthchecks in Dockerfile oder Compose-File, kombiniert mit automatischem Neustart über Tools wie autoheal und serverweiter Metrik-Erfassung über cAdvisor und node_exporter, verwandeln reaktives Feuerlöschen in proaktive Fehlererkennung. Wer diese fünf Bereiche einmal sauber aufsetzt und in eine gemeinsame daemon.json sowie eine Compose-Basis-Konfiguration überführt, muss sie nicht für jeden neuen Container erneut einzeln durchdenken.
Docker auf einem Linux-Server produktiv betreiben: Das Wichtigste auf einen Blick
Log-Rotation
max-size/max-file in /etc/docker/daemon.json setzen, sonst füllt json-file die Root-Partition.
Ressourcen-Limits
--memory, --cpus, --pids-limit pro Container, damit einer nicht den ganzen Host starvt.
systemd-Integration
Restart-Policy unless-stopped oder eigenes Unit-File mit After=network-online.target.
Monitoring & Healthchecks
HEALTHCHECK, autoheal und cAdvisor/node_exporter für automatische Erkennung und Reaktion.