statt nur Ports zu öffnen
Ein offener Port bedeutet nicht, dass eine Anwendung wirklich bereit ist. Docker Healthchecks geben Containern eine eigene Stimme – sie melden, wann ein Service tatsächlich Anfragen verarbeiten kann, wann er degradiert ist und wann ein Neustart nötig wird. Ohne diesen Mechanismus bleibt Container-Orchestrierung blind.
Inhaltsverzeichnis
- 1. Das Problem mit Port-Checks allein
- 2. Die HEALTHCHECK-Direktive im Dockerfile
- 3. Interval, Timeout, Retries und Startperiod verstehen
- 4. Sinnvolle Healthcheck-Kommandos jenseits von curl
- 5. Docker Compose: depends_on mit condition
- 6. Healthchecks für Datenbanken und Message Broker
- 7. Healthchecks in Docker Swarm und Restart-Policies
- 8. Healthcheck-Status auslesen und debuggen
- 9. Healthcheck-Strategien im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Das Problem mit Port-Checks allein
Der häufigste Irrtum in Docker-Deployments: Ein Container, der seinen Port öffnet, ist betriebsbereit. In der Realität können Anwendungen einen TCP-Port binden, lange bevor ihre Datenbankverbindung steht, Konfigurationsdateien geladen sind oder interne Caches aufgewärmt wurden. Wer Abhängigkeiten zwischen Containern nur über depends_on ohne condition: service_healthy löst, startet nachfolgende Services gegen einen nicht fertigen Upstream – mit kaskadierenden Startfehlern als Ergebnis.
Der Docker Healthcheck ist die offizielle Antwort auf dieses Problem. Statt extern zu prüfen, ob ein Port offen ist, führt Docker in einem definierten Intervall einen Befehl im Container selbst aus. Gibt dieser Befehl Exit-Code 0 zurück, gilt der Container als healthy. Gibt er Exit-Code 1 zurück, gilt er als unhealthy. Exit-Code 2 ist für zukünftige Verwendung reserviert und sollte nicht benutzt werden. Dieser einfache Mechanismus bildet die Basis für belastbare, selbstheilende Container-Infrastrukturen.
Die Abwesenheit eines Healthchecks bedeutet, dass der Container dauerhaft im Zustand health: starting verbleibt – was von vielen Teams als unkritisch ignoriert wird, aber in Orchestrierungsszenarien gravierende Folgen hat. Orchestratoren wie Docker Swarm nutzen den Healthcheck-Status aktiv, um Deployments zu steuern und unhealthy Container automatisch zu ersetzen.
2. Die HEALTHCHECK-Direktive im Dockerfile
Die HEALTHCHECK-Direktive gehört direkt ins Dockerfile und ist damit Teil des Images – nicht der Laufzeitkonfiguration. Das ist ein entscheidender Unterschied: Wer den Healthcheck im Image verankert, stellt sicher, dass jede Instanz des Containers – egal ob lokal, in Staging oder in Produktion – denselben Gesundheitstest durchläuft. Ein nachträgliches Hinzufügen in docker-compose.yml ist möglich und überschreibt den Image-seitigen Healthcheck, ist aber für wiederverwendbare Base-Images nicht empfehlenswert.
Die Syntax ist kompakt: HEALTHCHECK [OPTIONS] CMD command. Das CMD kann ein Shell-Kommando (CMD curl -f http://localhost/health) oder ein JSON-Array (CMD ["curl", "-f", "http://localhost/health"]) sein. Die JSON-Form ist vorzuziehen, da sie Shell-Injection vermeidet und keine Shell im Container voraussetzt. Mit HEALTHCHECK NONE lässt sich ein im Eltern-Image definierter Healthcheck explizit deaktivieren – sinnvoll für Test-Images oder Images, die als Sidecar ohne eigenen HTTP-Endpunkt laufen.
# Dockerfile — HEALTHCHECK for a PHP-FPM/Nginx web service
FROM php:8.4-fpm-alpine
# Install curl for health probes (keep layer small)
RUN apk add --no-cache curl
COPY ./app /var/www/html
COPY ./docker/php-fpm.conf /usr/local/etc/php-fpm.d/www.conf
# HEALTHCHECK: test every 30s, timeout after 5s, 3 retries before unhealthy
# startPeriod gives the container 60s grace time during startup
HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=60s \
CMD curl -fsS http://localhost/health || exit 1
EXPOSE 9000
CMD ["php-fpm"]
Ein häufiger Fehler ist das Fehlen des -f-Flags bei curl. Ohne -f gibt curl Exit-Code 0 zurück, auch wenn der HTTP-Statuscode 500 ist – der Healthcheck meldet den Container als healthy, obwohl die Anwendung Fehler ausgibt. Das Flag -s unterdrückt den Fortschrittsbalken, -S zeigt bei Fehlern dennoch eine Fehlermeldung an. Diese Kombination -fsS ist das empfohlene Muster für HTTP-basierte Healthchecks.
3. Interval, Timeout, Retries und Startperiod verstehen
Die vier Parameter der HEALTHCHECK-Direktive müssen aufeinander abgestimmt sein, damit ein Container weder zu früh als unhealthy eingestuft wird noch zu lange im degradierten Zustand bleibt. --interval legt fest, wie oft der Test ausgeführt wird; der Standardwert von 30 Sekunden ist für die meisten Webservices passend, für kritische Dienste sollte er auf 10–15 Sekunden reduziert werden. --timeout begrenzt die Ausführungszeit des Test-Kommandos; überschreitet der Test das Timeout, gilt er als fehlgeschlagen.
--retries definiert, wie viele aufeinanderfolgende Fehlschläge nötig sind, bevor der Container als unhealthy gilt. Mit dem Standardwert 3 und einem Intervall von 30 Sekunden vergehen also 90 Sekunden vom ersten Fehlschlag bis zum unhealthy-Status. Der wichtigste und am häufigsten vergessene Parameter ist --start-period: Er gibt dem Container eine Anlaufzeit, während der fehlgeschlagene Checks nicht auf die Retry-Zählung anrechnen. Ein Java-Service, der 45 Sekunden zum Start benötigt, sollte mindestens --start-period=60s erhalten – sonst wird er als unhealthy eingestuft, bevor er überhaupt fertig gestartet ist.
4. Sinnvolle Healthcheck-Kommandos jenseits von curl
Nicht jeder Container hat einen HTTP-Endpunkt, und curl ist nicht immer im Image verfügbar. Für schlanke Alpine-basierte Images ist der eingebaute wget mit wget -qO- http://localhost/health eine Alternative. Noch schlanker ist ein reines TCP-Check mit dem nc-Tool: nc -z localhost 3306 prüft, ob ein Port lauscht, ohne HTTP-Overhead. Für PHP-FPM ohne HTTP-Frontend gibt es das Tool cgi-fcgi, das direkt über das FastCGI-Protokoll einen Status abrufen kann.
Für Anwendungen ohne jegliche Netzwerkschnittstelle – etwa Worker-Prozesse oder Cron-Jobs – eignet sich ein Datei-basierter Healthcheck: Der Prozess schreibt regelmäßig einen Timestamp in eine temporäre Datei, der Healthcheck prüft, ob diese Datei existiert und ob der Timestamp nicht zu alt ist. Dieses Muster erkennt hängende Worker-Prozesse, die zwar laufen, aber keine Arbeit mehr verarbeiten – ein Zustand, der mit Port-Checks völlig unsichtbar wäre. Eine weitere Option für kritische Dienste: ein eigener Health-Endpoint im Applikationscode, der interne Subsysteme testet und Details über den Zustand zurückgibt.
# Various HEALTHCHECK patterns for different container types
# --- HTTP service with wget (no curl needed) ---
HEALTHCHECK --interval=15s --timeout=3s --retries=3 --start-period=30s \
CMD wget -qO- http://localhost:8080/health || exit 1
# --- TCP port check (no HTTP, just connectivity) ---
HEALTHCHECK --interval=20s --timeout=5s --retries=3 \
CMD nc -z localhost 3306 || exit 1
# --- Worker/cron: heartbeat file must exist and be fresh (< 120s old) ---
HEALTHCHECK --interval=60s --timeout=5s --retries=2 --start-period=10s \
CMD test -f /tmp/worker.heartbeat && \
test $(( $(date +%s) - $(stat -c %Y /tmp/worker.heartbeat) )) -lt 120 \
|| exit 1
# --- PHP-FPM status via cgi-fcgi (no HTTP server needed) ---
HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=20s \
CMD SCRIPT_NAME=/status SCRIPT_FILENAME=/status REQUEST_METHOD=GET \
cgi-fcgi -bind -connect 127.0.0.1:9000 | grep -q "^pool:" || exit 1
5. Docker Compose: depends_on mit condition
Docker Compose unterstützt seit Version 2.1 die condition-Eigenschaft unter depends_on, die zusammen mit Healthchecks echte Startreihenfolgen erzwingt. Ohne condition: service_healthy wartet Compose nur darauf, dass der Container gestartet wurde – nicht dass er bereit ist. Das Ergebnis sind Race Conditions beim Start: Die Anwendung versucht, auf eine Datenbank zuzugreifen, die den Port geöffnet hat, aber noch keine Verbindungen akzeptiert, weil der Initialisierungsprozess noch läuft.
Die drei verfügbaren Conditions sind service_started (Standardverhalten – Container läuft), service_healthy (Container ist im healthy-Status laut Healthcheck) und service_completed_successfully (Container hat mit Exit-Code 0 beendet – für Init-Container). Mit dieser Kombination lässt sich eine echte Startup-Sequenz modellieren: Datenbank läuft und ist bereit, dann Migrationsskript läuft durch, dann Applikation startet. Jede Abhängigkeit wird durch den Healthcheck des vorhergehenden Containers abgesichert.
# docker-compose.yml — Startup sequencing with healthcheck conditions
services:
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: shop
# Healthcheck: MySQL ready to accept connections
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-psecret"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
migrate:
image: myapp:latest
command: php bin/magento setup:upgrade
# Wait until db is healthy before running migrations
depends_on:
db:
condition: service_healthy
# Migration is a one-shot container — no healthcheck needed
app:
image: myapp:latest
ports:
- "8080:80"
# Wait for db healthy AND migration completed
depends_on:
db:
condition: service_healthy
migrate:
condition: service_completed_successfully
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s
6. Healthchecks für Datenbanken und Message Broker
Datenbanken sind die häufigsten Abhängigkeiten in Container-Setups, und sie haben spezifische Anforderungen an ihren Healthcheck. MySQL und MariaDB bieten mysqladmin ping als eingebauten Befehl, der die Verbindungsbereitschaft des Servers direkt testet. PostgreSQL hat pg_isready, das prüft, ob der Server bereit ist, Verbindungen anzunehmen. Beide Kommandos sind deutlich zuverlässiger als ein reiner TCP-Check, weil sie das Datenbankprotokoll sprechen und einen wirklich betriebsbereiten Service bestätigen.
Redis bietet redis-cli ping, das mit der Antwort PONG die Betriebsbereitschaft bestätigt. RabbitMQ hat ein eingebautes Management-Interface, dessen /api/healthchecks/node-Endpunkt den Cluster-Status zurückgibt. Für Kafka gibt es keine so direkten Werkzeuge im Image, aber ein Python- oder Shell-Skript, das einen Producer-Test durchführt, kann als Healthcheck dienen. Der gemeinsame Grundsatz für alle Message-Broker-Healthchecks: nicht nur testen ob der Port offen ist, sondern ob das Protokoll antwortet.
7. Healthchecks in Docker Swarm und Restart-Policies
In Docker Swarm entfalten Healthchecks ihre volle Wirkung: Swarm überwacht kontinuierlich den Status aller Container in einem Service. Wird ein Container unhealthy, markiert Swarm die Task als fehlerhaft und startet eine neue Instanz – auf einem anderen Node, falls verfügbar. Dieser Automatismus ist der Kern der Selbstheilung in Swarm-Deployments. Ohne Healthchecks bleibt ein degradierter Container im Routing-Pool und nimmt weiterhin Anfragen an, die er nicht korrekt verarbeiten kann.
Restart-Policies ergänzen Healthchecks auf der Einzelcontainer-Ebene. restart: unless-stopped in Compose oder --restart on-failure:3 in Swarm definieren, was nach einem Container-Absturz passiert. Der Unterschied zum Healthcheck-basierten Ersetzen: Restart-Policies greifen bei Prozess-Abstürzen (Exit-Code ungleich 0), während Healthchecks auch hängende Prozesse erkennen, die nicht abstürzen, aber keine sinnvolle Arbeit mehr leisten. Beide Mechanismen zusammen ergeben eine belastbare Selbstheilungsschicht.
8. Healthcheck-Status auslesen und debuggen
Der aktuelle Healthcheck-Status eines Containers ist über docker inspect vollständig abrufbar. Der JSON-Pfad .State.Health enthält den aktuellen Status (healthy, unhealthy, starting), den letzten Exit-Code, die Ausgabe des letzten Tests und die letzten fünf Testergebnisse mit Zeitstempel. Dieser Verlauf ist beim Debugging unschätzbar: Er zeigt, wann ein Container instabil wurde, was die Healthcheck-Ausgabe war und wie lange der degradierte Zustand schon anhält.
Ein häufiges Debugging-Problem: Der Healthcheck schlägt fehl, aber manuell funktioniert das Kommando. Ursache ist oft, dass der Healthcheck als Root-User ausgeführt wird, die Anwendung aber als anderer User läuft und Firewall-Regeln oder iptables-Konfigurationen unterschiedliche Zugriffsrechte erzeugen. Ein anderes häufiges Problem: DNS-Auflösung funktioniert im Healthcheck anders als in der Anwendung, weil der Healthcheck-Prozess eine andere Netzwerk-Namespace-Ansicht hat. docker exec mit dem User des Healthcheck-Prozesses ist die verlässlichste Methode zum Replizieren.
# Inspect healthcheck state — full details
docker inspect --format='{ {json .State.Health} }' my-container | jq .
# Quick status check
docker inspect --format='{ {.State.Health.Status} }' my-container
# Watch health status in real time (updates every 5s)
watch -n 5 "docker inspect --format='{ {.State.Health.Status} } — { {(index .State.Health.Log 0).Output} }' my-container"
# List all containers with their health status
docker ps --format "table { {.Names} }\t{ {.Status} }\t{ {.Ports} }"
# Manually run the healthcheck command to debug failures
# (runs as same user as the healthcheck process)
docker exec my-container curl -fsS http://localhost/health
# Show last 5 healthcheck results with timestamps
docker inspect my-container | jq '.[0].State.Health.Log[] | {Start, ExitCode, Output}'
9. Healthcheck-Strategien im Vergleich
Verschiedene Check-Strategien haben unterschiedliche Eignung je nach Container-Typ. Die Wahl des richtigen Healthcheck-Ansatzes hat direkte Auswirkungen auf die Erkennungsgeschwindigkeit und den Overhead.
| Strategie | Geeignet für | Kommando | Tiefe |
|---|---|---|---|
| HTTP-Endpunkt | Web-Services, APIs | curl -fsS /health | Anwendungslogik prüfbar |
| TCP-Port | Datenbanken, Cache | nc -z host port | Nur Verbindungsbereitschaft |
| Protokoll-Native | MySQL, Redis, PG | mysqladmin ping | Echte Betriebsbereitschaft |
| Heartbeat-Datei | Worker, Cron-Jobs | test -f + stat timestamp | Erkennt hängende Prozesse |
| Prozess-Check | Sidecar-Container | kill -0 $(cat app.pid) | Nur Prozessexistenz |
Die Kombination aus protokoll-nativem Check für Datenbanken und HTTP-Check für Webservices deckt den Großteil der Praxisfälle ab. Für Worker-Container ohne Netzwerkschnittstelle ist der Heartbeat-Datei-Ansatz die einzige Methode, die auch logisch hängende Prozesse erkennt. Wer nur auf Prozess-Existenz prüft, hat keinen echten Healthcheck – ein abgestürzter und neu gestarteter Prozess, der im Startup-Loop feststeckt, besteht diesen Test dauerhaft.
Mironsoft
Docker-Infrastruktur, Container-Observability und Deployment-Automatisierung
Container, die wissen, wann sie bereit sind?
Wir analysieren euer Docker-Setup, ergänzen sinnvolle Healthchecks für alle Service-Typen und konfigurieren Compose-Abhängigkeiten, die Race Conditions beim Start zuverlässig eliminieren.
Healthcheck-Audit
Analyse aller Container-Typen und Empfehlung passender Check-Strategien
Compose-Sequencing
depends_on mit condition und Startup-Reihenfolge für komplexe Service-Graphen
Swarm-Monitoring
Self-Healing-Konfiguration mit Restart-Policies und Healthcheck-Alerting
10. Zusammenfassung
Docker Healthchecks lösen ein grundlegendes Problem der Container-Orchestrierung: die Unterscheidung zwischen einem laufenden und einem tatsächlich betriebsbereiten Container. Die HEALTHCHECK-Direktive im Dockerfile verankert den Test im Image und stellt sicher, dass er in jeder Umgebung ausgeführt wird. Die Parameter --interval, --timeout, --retries und --start-period müssen auf die Startzeit und das Verhalten des Services abgestimmt sein – insbesondere --start-period wird häufig vergessen und führt zu falschen unhealthy-Einstufungen.
Docker Compose mit depends_on: condition: service_healthy nutzt den Healthcheck-Status, um echte Startreihenfolgen zu erzwingen und Race Conditions zu eliminieren. In Docker Swarm bilden Healthchecks die Grundlage für automatisches Ersetzen degradierter Container. Das Debugging über docker inspect .State.Health liefert die letzten fünf Testergebnisse mit Zeitstempel und Ausgabe – genug Information, um Probleme schnell zu lokalisieren.
Docker Healthchecks — Das Wichtigste auf einen Blick
HEALTHCHECK-Direktive
Im Dockerfile verankern – mit --start-period für Anlaufzeit und -f-Flag bei curl, damit HTTP-5xx als Fehler erkannt wird.
Compose depends_on
condition: service_healthy erzwingt echte Bereitschaft – nicht nur Container-Start. Race Conditions werden strukturell eliminiert.
Check-Strategie
HTTP-Check für Web-Services, protokoll-nativ für Datenbanken, Heartbeat-Datei für Worker ohne Netzwerkschnittstelle.
Debugging
docker inspect .State.Health liefert die letzten 5 Testergebnisse. Healthcheck manuell mit docker exec replizieren, um Umgebungsunterschiede zu finden.