Docker HEALTHCHECK und restart_policy: wann Container sich selbst heilen – und wann nicht
HEALTHCHECK-Optionen, restart_policy-Modi und depends_on: condition: service_healthy erklärt – inklusive Timing-Formel, einer verbatim-Fehlermeldung und dem Gotcha, das die meisten übersehen.
Ein Container mit Status running ist kein Beweis dafür, dass die Anwendung antwortet. Docker überwacht, ob der Hauptprozess noch läuft — nicht, ob er sinnvolle Antworten liefert. Eine Datenbank kann hochgefahren sein, aber noch keine Verbindungen akzeptieren. Eine API kann gestartet sein, aber auf ein fehlendes Secret warten. Mit HEALTHCHECK geben Sie Docker ein konkretes Prüfprogramm: Führe diesen Befehl aus, prüfe den Exit-Code, und ändere den Gesundheitszustand des Containers entsprechend. restart_policy entscheidet, was nach einem Absturz passiert — hat aber eine Einschränkung, die viele überrascht.
Was Docker ohne Healthcheck nicht sehen kann
running ≠ funktionsfähig
docker ps zeigt Up 3 minutes und Status: running. Das sagt über die Anwendung genau eines aus: Der Prozess läuft noch. Ein Node.js-Server, dessen Datenbankverbindung abgebrochen ist und der auf jeden Request mit 500 Internal Server Error antwortet, ist aus Sicht von Docker healthy — weil kein Healthcheck das Gegenteil feststellt. Ein PostgreSQL-Container, der noch im Initialisierungsmodus ist und keine Verbindungen annimmt, ist ebenfalls healthy. Das ist der blinde Fleck, und gerade bei abhängigen Services im selben Stack führt er zu schwer nachvollziehbaren Startfehlern.
Der blinde Fleck kostet im schlimmsten Fall bares Geld: Wer wissen möchte, was eine Stunde ungeplanter Ausfallzeit im eigenen Betrieb konkret bedeutet, kann das mit dem Ausfallkosten-Rechner schnell einschätzen.
Welche Services besonders von Healthchecks profitieren
Am stärksten fallen Healthchecks dort auf, wo andere Services auf einen Dienst warten: Datenbanken, Message Queues, Caches. Wer im Compose-Stack ein depends_on verwendet, bekommt ohne Healthcheck nur eine Startsequenz — nicht die Garantie, dass der Abhängigkeitsdienst betriebsbereit ist. Erst depends_on: condition: service_healthy liefert diese Garantie, und dafür braucht der Abhängigkeitsdienst einen konfigurierten Healthcheck. Auch für Services mit langsamer Initialisierung — JVM-Applikationsserver, Django-Migrationen, Elasticsearch-Cluster — lohnt ein sorgfältig eingestellter Healthcheck.
HEALTHCHECK schreiben: Dockerfile und Compose-Syntax im Vergleich
Optionen und ihre Standardwerte
Alle fünf Optionen gelten für Dockerfile und Compose-YAML gleichermaßen. Docker Engine 29.8.1 (Stand September 2026) verwendet folgende Standardwerte, die im Moby-Quellcode unter daemon/health.go definiert sind:
| Option | Standardwert | Bedeutung |
|---|---|---|
interval |
30s | Abstand zwischen zwei Prüfungen |
timeout |
30s | Maximale Laufzeit des Prüfbefehls |
retries |
3 | Aufeinanderfolgende Fehlschläge bis unhealthy |
start_period |
0s | Anlaufzeit: Fehlschläge zählen in dieser Zeit nicht |
start_interval |
5s | Prüfintervall während start_period (Docker ≥ 25.0) |
Docker kennt drei Gesundheitszustände für einen Container: starting (noch in der Anlaufphase), healthy (letzte Prüfung erfolgreich) und unhealthy (zu viele aufeinanderfolgende Fehlschläge). Kein Healthcheck konfiguriert? Der Container hat gar keinen Status in diesem Sinne — docker inspect zeigt dann keinen Health-Schlüssel.
Dockerfile-Syntax:
HEALTHCHECK --interval=10s --timeout=5s --retries=5 \
--start-period=30s --start-interval=5s \
CMD pg_isready -U app -d appdb
Beachten Sie: Im Dockerfile werden Bindestriche verwendet (--start-period), in der Compose-YAML Unterstriche (start_period).
Compose-Syntax:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
start_interval: 5s
CMD vs. CMD-SHELL: wann brauchen Sie eine Shell?
["CMD", "pg_isready", "-U", "app"] führt den Befehl direkt aus — kein Shell-Overhead, keine Variablenexpansion. Das ist die richtige Form, wenn das Werkzeug direkt aufgerufen werden kann. ["CMD-SHELL", "pg_isready -U app || exit 1"] führt den Ausdruck via /bin/sh -c aus und erlaubt Shell-Features wie ||, Pipes und Parametersubstitution.
Distroless- und Scratch-Images haben keine Shell. In diesen Images schlägt CMD-SHELL mit exec: "/bin/sh": stat /bin/sh: no such file or directory fehl. Wer sein Image nach dem Multi-Stage-Muster aufbaut und Werkzeuge bewusst aus dem Runtime-Image herausgelassen hat, sollte das im Hinterkopf behalten: Entweder eine statische Healthcheck-Binary mitkopieren oder einen datenbankspezifischen Befehl verwenden, der bereits im Image ist.
Ein häufiger Fehler ist curl --fail http://localhost/health als Healthcheck in einem Image, das kein curl enthält. Docker protokolliert dann in .State.Health.Log die folgende Fehlermeldung:
OCI runtime exec failed: exec failed: unable to start container process:
exec: "curl": executable file not found in $PATH
Nach --retries Fehlschlägen steht der Container auf unhealthy. Besser: wget -qO- http://localhost/health (häufiger in Alpine-Images vorhanden als curl), pg_isready für PostgreSQL, redis-cli PING für Redis.
start_period und start_interval: schnelles Polling beim Hochfahren
start_period definiert eine Anlaufzeit, in der Fehlschläge nicht gegen --retries gezählt werden — gedacht für Services mit langsamer Initialisierung: Datenbankmigrationen, JVM-Warmup, Elasticsearch-Cluster-Aufbau. start_interval erlaubt während dieser Anlaufzeit häufigeres Polling als im Normalbetrieb (eingeführt mit Docker Engine 25.0).
Wichtige Einschränkung: start_interval wird stillschweigend ignoriert, wenn start_period auf 0s steht — dem Standardwert. Wer start_interval: 5s setzt und kein start_period angibt, bekommt kein schnelleres Polling. Dieses Verhalten ist dokumentiert (moby/moby Issue #49900), steht aber nicht prominent in der offiziellen Prose-Dokumentation.
restart_policy: vier Modi mit einem entscheidenden Unterschied
Vergleichstabelle
| Wert | Startet nach Absturz neu? | Startet nach docker compose down + Daemon-Neustart? |
|---|---|---|
no (Standard) |
Nein | Nein |
on-failure |
Ja, bei Exit-Code ≠ 0 | Nein |
unless-stopped |
Ja | Nur wenn kein manueller Stop vorausging |
always |
Ja | Immer — auch nach manuellem Stop |
Warum unless-stopped für selbst gehostete Dienste die bessere Wahl ist
always und unless-stopped verhalten sich im Normalbetrieb identisch. Der Unterschied: Mit restart: always startet Docker den Container nach einem System-Reboot auch dann wieder, wenn Sie ihn vorher manuell mit docker compose down gestoppt haben. Mit restart: unless-stopped merkt sich Docker den manuellen Stop — der Container bleibt nach einem Reboot unten. Das ist das gewünschte Verhalten für selbst gehostete Dienste: automatisch hochkommen nach einem Reboot, aber docker compose down bedeutet wirklich, dass der Stack unten bleibt. In der Authelia-SSO-Konfiguration — wo ein versehentlicher Neustart einer Forward-Auth-Komponente den Zugang zu allen dahinter liegenden Services sperren kann — ist das der entscheidende Unterschied.
Das wichtigste Missverständnis zum Thema: restart_policy in standalone Docker Compose reagiert auf den Exit-Code des Hauptprozesses — nicht auf den Healthcheck-Status. Ein Container, der läuft aber unhealthy ist, wird von Compose nicht neu gestartet. Nur Docker Swarm (deploy.restart_policy) berücksichtigt den Gesundheitsstatus als Neustart-Trigger. Für standalone Setups brauchen Sie entweder ein Wrapper-Skript, das den Prozess aktiv beendet, wenn ein Health-Check dauerhaft fehlschlägt, oder ein externes Monitoring-Tool.
Ein durchgehendes Minimalbeispiel: Postgres und eine Web-App
compose.yaml Schritt für Schritt
Das folgende Beispiel zeigt einen Postgres-Container mit sorgfältig konfiguriertem Healthcheck und eine Web-App, die erst startet, wenn die Datenbank tatsächlich bereit ist:
services:
db:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: appdb
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
start_interval: 5s
volumes:
- db_data:/var/lib/postgresql/data
web:
image: ghcr.io/example/myapp:latest
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "8080:8080"
environment:
DATABASE_URL: postgres://app:secret@db:5432/appdb
volumes:
db_data:
pg_isready ist im offiziellen Postgres-Image enthalten und der zuverlässigste Test dafür, ob Postgres Verbindungen akzeptiert. Es gibt Exit-Code 0 zurück, wenn der Server erreichbar ist, und Exit-Code 1 bei Problemen. Kein curl, keine HTTP-Anfrage, kein zusätzliches Paket nötig.
depends_on: condition: service_healthy
Wenn docker compose up ausgeführt wird, wechselt web in den Created-Zustand und wartet. Compose prüft regelmäßig den Gesundheitsstatus von db. Erst wenn db healthy meldet, startet web. Ohne condition: service_healthy würde web unmittelbar nach db starten — auch wenn Postgres noch initialisiert.
+--------+ depends_on +----------+
| web | -----------> | db |
|(Created) |(starting)|
+--------+ +----------+
| |
| [pg_isready OK]
| |
| +---------+
+------ startet <--| db |
|(healthy)|
+---------+
Neben service_healthy unterstützt depends_on zwei weitere Bedingungen: service_started (entspricht der Kurzform depends_on: [db]) und service_completed_successfully für Initialisierungs- oder Migrations-Container, die nach Abschluss beendet werden sollen.
Timing und Zustände messen
Wie lange bis unhealthy? Die Formel
Mit den Standardwerten (interval=30s, retries=3, start_period=0s) dauert es maximal 90 Sekunden, bis ein Container unhealthy wird: drei aufeinanderfolgende Fehlschläge à 30 Sekunden. Im Minimalbeispiel oben (start_period=30s, start_interval=5s, interval=10s, retries=5) sieht die Rechnung so aus:
- Während
start_period(30 s): Prüfungen alle 5 s, Fehlschläge werden nicht gewertet - Nach
start_period: Prüfungen alle 10 s, 5 aufeinanderfolgende Fehlschläge nötig - Worst Case bis
unhealthy: 30 s + 5 × 10 s = 80 Sekunden
Diese Formel ist relevant für depends_on: condition: service_healthy: Compose wartet, bis der Dienst healthy wird, oder bricht ab, wenn das interne Warte-Timeout überschritten wird. Wer lange Startzeiten hat, sollte start_period entsprechend großzügig ansetzen — ein zu niedrig angesetzter Wert führt zu falschen unhealthy-Meldungen schon beim Erststart.
docker inspect und docker events
Den aktuellen Gesundheitsstatus eines laufenden Containers prüfen:
docker inspect --format='{{json .State.Health}}' <container_name> | jq .
Die Ausgabe enthält Status, FailingStreak und die letzten fünf Prüfergebnisse (Log) mit Exit-Code, Start- und Endzeitpunkt sowie der vollständigen Ausgabe des Prüfbefehls. Das ist die erste Anlaufstelle, wenn ein Service unerwartet unhealthy wird. Docker kürzt die Ausgabe je Log-Eintrag auf 4096 Bytes — Prüfbefehle, die viel auf stdout schreiben, sollten mit > /dev/null oder 2>&1 beruhigt werden.
Echtzeit-Übergänge beobachten:
docker events --filter event=health_status
Dieser Stream zeigt jeden Übergang zwischen starting, healthy und unhealthy mit Zeitstempel — nützlich beim Debuggen von Timing-Problemen in depends_on-Ketten. Eine Übersicht über alle laufenden Container inklusive Healthcheck-Kurzstatus liefert docker ps --format "table {{.Names}}\t{{.Status}}".
Häufige Fragen
Löst ein unhealthy-Status automatisch einen Neustart aus?
Nein. restart_policy in standalone Docker Compose reagiert auf den Exit-Code des Hauptprozesses, nicht auf den Healthcheck-Status. Ein Container, der läuft aber unhealthy ist, wird nicht neu gestartet. Nur Docker Swarm (deploy.restart_policy) berücksichtigt den Gesundheitsstatus. Für standalone Setups muss die Anwendung selbst den Prozess beenden, wenn sie nicht mehr funktionsfähig ist.
Kann ich den Healthcheck eines Basis-Images deaktivieren?
Ja: im Dockerfile mit HEALTHCHECK NONE, in der compose.yaml mit disable: true. Nützlich, wenn ein Debug-Basis-Image einen Healthcheck mitbringt, der im Produktionsbetrieb nicht passt, oder wenn ein externer Mechanismus (Kubernetes Liveness Probe, AWS ECS) das Monitoring übernimmt.
Was tun, wenn der Healthcheck-Befehl im Image nicht verfügbar ist?
Am häufigsten fehlt curl in schlanken Alpine- oder Distroless-Images. Alternativen: wget, pg_isready für PostgreSQL, redis-cli PING für Redis. Wer ein Image nach dem Multi-Stage-Muster aufbaut, kann eine statische Binary in der Build-Stage herunterladen und in die Runtime-Stage kopieren.
Kann depends_on auf mehrere Dienste mit service_healthy warten?
Ja. Die Langform erlaubt beliebig viele Abhängigkeiten:
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
migrations:
condition: service_completed_successfully
service_completed_successfully eignet sich für Migrations-Container, die nach Abschluss mit Exit-Code 0 beendet werden sollen. Die Web-App startet erst, wenn sowohl Datenbank als auch Cache healthy melden und die Migrations durchgelaufen sind.
Warum schlägt der Healthcheck sporadisch fehl, obwohl der Service läuft?
Häufigste Ursache: timeout ist zu niedrig. Der Prüfbefehl läuft unter Last länger als erwartet — besonders bei HTTP-Requests gegen eine beschäftigte Anwendung. Ein realistischer Wert liegt zwischen 5 s und 10 s. Die tatsächliche Laufzeit jeder Prüfung ist im Log-Array von docker inspect ablesbar.
Weiterführende Dokumentation
Den vollständig verdrahteten Compose-Stack — eigenes Netzwerk, Volumes, Service-Abhängigkeiten — zeigt der Docker Compose Multi-Service-Post. Wie man Images schlankhält und Werkzeuge wie pg_isready gezielt in Runtime-Images verfügbar macht, zeigt das Multi-Stage-Dockerfile-Beispiel. Für Backups des laufenden Stacks beschreibt der Restic-Beitrag den Weg zu verschlüsselten, automatisierten Sicherungen.
Offizielle Referenz: HEALTHCHECK im Dockerfile · healthcheck in Compose · restart in Compose.
Fragen zu stabilen Container-Stacks beantwortet der IT-Support.
Hinweis: Die Beiträge dieses Blogs werden unter Einsatz von KI erstellt und vor der Veröffentlichung redaktionell geprüft. Die redaktionelle Verantwortung trägt Emre Yurtbay (siehe Impressum).