Docker Healthchecks praktisch nutzen statt nur Ports zu öffnen
AI generated
Docker · Container · Monitoring · DevOps
Docker Healthchecks praktisch nutzen
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.

12 Min. Lesezeit HEALTHCHECK · Startperiod · Retries · depends_on · Compose Docker 25+ · Compose v2

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.

11. FAQ: Docker Healthchecks

1Unterschied Healthcheck vs. Port-Check?
Port-Check: TCP-Port offen. Healthcheck: Anwendung wirklich bereit – Datenbankverbindung vorhanden, Konfiguration geladen, Anfragen verarbeitbar.
2Healthcheck schlägt direkt nach Start fehl?
--start-period fehlt oder ist zu kurz. Setze ihn auf Startzeit + Puffer, damit fehlgeschlagene Checks während des Starts nicht auf die Retry-Zählung anrechnen.
3Kein curl im Image – was tun?
wget, nc für TCP, protokoll-native Tools (mysqladmin, redis-cli) oder ein eigenes kleines Health-Binary ohne Shell-Abhängigkeit verwenden.
4Was bedeutet Zustand 'starting'?
Noch kein erfolgreicher Check abgeschlossen. Ohne HEALTHCHECK-Direktive bleibt der Container permanent in 'starting' – er bekommt nie einen health-Status.
5HEALTHCHECK NONE vs. keine Direktive?
HEALTHCHECK NONE deaktiviert explizit auch geerbte Checks aus dem Eltern-Image. Ohne Direktive wird der geerbte Check übernommen.
6Was passiert bei unhealthy in Swarm?
Swarm plant neue Task auf verfügbarem Node, entfernt unhealthy Container aus dem Routing – bevor die neue Instanz healthy ist.
7Healthcheck in docker-compose.yml überschreiben?
Ja – healthcheck-Block überschreibt den Image-Healthcheck vollständig. Mit 'disable: true' deaktivieren, etwa für schnelle lokale Entwicklungsstarts.
8Letzte Healthcheck-Ergebnisse auslesen?
docker inspect --format='{ {json .State.Health} }' container | jq . — Status, FailingStreak und letzte 5 Ergebnisse mit Zeitstempel und Ausgabe.
9Welchen Exit-Code soll mein Check zurückgeben?
0 = healthy, 1 = unhealthy. Exit-Code 2 ist reserviert und sollte nicht verwendet werden. Alles außer 0 gilt als unhealthy.
10Healthcheck für Worker ohne HTTP-Endpunkt?
Worker schreibt Timestamp in Datei, Healthcheck prüft Existenz und Aktualität. Erkennt hängende Prozesse, die laufen aber keine Arbeit verarbeiten.