Design wechseln

Docker Compose Service-Abhängigkeiten: Healthcheck-Konfiguration löst Startreihenfolge-Probleme mit Datenbanken

Easton editorial illustration: environment switchboard

Freitagabend, zehn Uhr – in Ihrem Terminal rollen dieselben Fehlermeldungen schon zum zehnten Mal vorbei.

Der Anwendungscontainer startet ständig neu, die Datenbank läuft zwar, ist aber immer einen Schritt zu langsam. Sie prüfen die docker-compose.yml – depends_on ist konfiguriert. Warum funktioniert es trotzdem nicht?

Dieses Problem kennen viele Entwickler. In der lokalen Entwicklungsumgebung schlägt docker-compose up die ersten beiden Starts oft fehl; erst nach zehn bis zwanzig Sekunden und mehreren Neustarts läuft alles stabil.

Die Ursache ist einfach: Docker depends_on steuert nur die Container-Startreihenfolge, nicht ob der Dienst wirklich bereit ist.

In diesem Artikel zeige ich Ihnen Schritt für Schritt:

  • die drei condition-Konfigurationen für depends_on (90 % kennen nur den Standardwert)
  • die korrekte healthcheck-Konfiguration für PostgreSQL und MySQL (mit vollständigen Beispielen)
  • die moderne Alternative zu wait-for-it-Skripten
  • eine Checkliste zur Fehlerbehebung für zuverlässige Container-Starts

Warum depends_on nicht ausreicht: Start ≠ Bereitschaft

In der Docker-Dokumentation steht ein oft übersehener Satz:

Compose does not wait until a container is “ready”, only until it’s running.

Bedeutung: Compose wartet nur, bis der Container läuft – nicht, bis der Dienst tatsächlich nutzbar ist.

Zeitlücke zwischen Container-Start und Service-Bereitschaft

Stellen Sie sich den Startvorgang eines PostgreSQL-Containers vor:

  1. 0 s: Docker startet den Container, postgres-Prozess startet ← depends_on gibt hier schon frei
  2. 2 s: Initialisierung des Datenverzeichnisses
  3. 5 s: Laden der Konfigurationsdateien
  4. 8 s: Ausführung von Init-Skripten (falls vorhanden)
  5. 12 s: endlich bereit, Verbindungen werden akzeptiert

Dazwischen liegt eine Lücke von rund 12 Sekunden. Versucht Ihre Web-App in Sekunde 1 eine Verbindung zur Datenbank, ist das Ergebnis zwangsläufig „Connection refused”.

In der Praxis kann es noch extremer sein. Bei einem Legacy-Projekt, das ich betreute, importierte ein Initialisierungsskript 500 MB Testdaten – allein das dauerte 40 Sekunden. Mit der Standard-depends_on-Konfiguration musste der Anwendungscontainer mindestens fünfmal abstürzen und neu starten, bevor die Verbindung klappte.

Die drei condition-Werte von depends_on

Viele wissen nicht, dass depends_on drei condition-Werte unterstützt:

services:
  web:
    depends_on:
      db:
        condition: service_started  # Standard: Container läuft, fertig
        # condition: service_healthy  # Warten auf bestandenen Healthcheck
        # condition: service_completed_successfully  # Warten auf erfolgreichen Exit (für Init-Container)

service_started (Standard): Sobald der Container im Status running ist, geht es weiter. Deshalb reicht depends_on allein oft nicht aus.

service_healthy: Der Healthcheck muss bestanden sein, der Container-Status muss „healthy” sein. Genau das brauchen wir in den meisten Fällen.

service_completed_successfully: Wartet auf erfolgreichen Exit (Exit-Code 0). Geeignet für einmalige Aufgaben wie Datenmigrationen.

Warum ist service_healthy nicht der Standard?

Sie fragen sich vielleicht: Wenn service_healthy so nützlich ist – warum ist es nicht der Standard?

Zwei Gründe:

  1. Nicht jeder Dienst braucht einen Healthcheck (z. B. reine stateless Worker)
  2. Healthchecks müssen Sie selbst konfigurieren – Docker weiß nicht, wann Ihr Dienst „bereit” ist

Das führt zum nächsten Thema: Wie konfiguriert man Healthchecks?

Healthcheck – vollständige Konfigurationsanleitung

Das Prinzip ist einfach: Docker führt regelmäßig einen Befehl aus. Exit-Code 0 bedeutet gesund, Exit-Code 1 bedeutet ungesund.

Vollständige healthcheck-Konfiguration

services:
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]  # Prüfbefehl
      interval: 10s       # alle 10 Sekunden prüfen
      timeout: 5s         # Timeout pro Prüfung: 5 Sekunden
      retries: 3          # erst nach 3 Fehlversuchen als unhealthy markieren
      start_period: 30s   # Fehler in den ersten 30 Sekunden zählen nicht zu retries

Alle fünf Parameter sind wichtig – gehen wir sie der Reihe nach durch.

test: Prüfbefehl

Es gibt zwei Formate:

# Variante 1: Shell verwenden (empfohlen)
test: ["CMD-SHELL", "pg_isready -U postgres"]

# Variante 2: Befehl direkt ausführen (ohne Shell)
test: ["CMD", "pg_isready", "-U", "postgres"]

In den meisten Fällen reicht CMD-SHELL, weil Sie Shell-Features wie Pipes und Umleitungen nutzen können.

Häufige Falle: Das Prüfwerkzeug muss im Image vorhanden sein. Prüfen Sie einen HTTP-Endpunkt mit curl, das Image enthält aber kein curl – der Healthcheck schlägt dauerhaft fehl. Mir ist das schon passiert; die Lösung war eine Zeile RUN apk add curl im Dockerfile.

interval: Prüfintervall

Wie oft geprüft wird. Zu häufig kostet Ressourcen, zu selten reagiert das System träge.

  • 10 s ist ein guter Standard für die meisten Szenarien
  • Bei kritischen Datenbanken können 5 s sinnvoll sein
  • Bei leichten Diensten auch 15–30 s

timeout: Timeout pro Prüfung

Maximale Wartezeit für eine einzelne Prüfung. Hängt der Befehl, wartet Docker bis zum Timeout und bricht dann ab.

Zu kurz führt zu Fehlalarmen, zu lang verlangsamt die Fehlererkennung. 5–10 s sind ein sicherer Bereich.

retries: Anzahl der Fehlversuche

Wie oft hintereinander fehlgeschlagen werden muss, bevor der Status unhealthy wird.

Das ist ein Entprellmechanismus. Netzwerk-Jitter oder kurzzeitige Lastspitzen können einzelne Prüfungen scheitern lassen – retries machen das System robuster.

3–5 ist sinnvoll. retries=1 ist zu empfindlich, retries=10 zu träge.

start_period: Start-Schonfrist (am leichtesten zu übersehen)

Fehler innerhalb von start_period zählen nicht zu retries. Der Dienst bekommt so eine „Start-Pufferzeit”.

Warum wichtig? Datenbanken brauchen Startzeit. PostgreSQL initialisiert Datenverzeichnisse, MySQL lädt Tabellenindizes. Ohne start_period zählt der Healthcheck schon in Sekunde 2 Fehler – möglicherweise ist der Dienst unhealthy, bevor retries überhaupt ausgeschöpft sind.

Empfohlene Werte:

  • PostgreSQL/MySQL: 30–60 s
  • Leichte Dienste (Redis): 15–30 s
  • Viele Init-Skripte: bis 120 s

Ich setze meist 60 s – lieber etwas länger warten als Fehlalarme riskieren.

Häufige Fehler und wie Sie sie vermeiden

Fehler 1: Umgebungsvariablen falsch referenziert

# ❌ Falsch: Compose interpoliert vor dem Start – der Container bekommt den Wert vom Host
test: ["CMD", "mysqladmin", "ping", "-p$MYSQL_ROOT_PASSWORD"]

# ✅ Richtig: $$ escapen, damit die Shell im Container auflöst
test: ["CMD-SHELL", "mysqladmin ping -p$$MYSQL_ROOT_PASSWORD"]

Fehler 2: start_period zu kurz

# ❌ Datenbank noch nicht initialisiert, Fehlerzähler läuft schon
healthcheck:
  test: ["CMD", "pg_isready"]
  interval: 5s
  retries: 3
  start_period: 10s  # zu kurz!

# ✅ Genug Startzeit einplanen
healthcheck:
  start_period: 60s  # deutlich entspannter

Fehler 3: Prüfwerkzeug fehlt im Image

Dieser Fehler ist besonders tückisch, weil Docker still scheitert.

# ❌ Wenn curl im Image fehlt, schlägt der Healthcheck dauerhaft fehl
test: ["CMD", "curl", "-f", "http://localhost/health"]

# ✅ Werkzeug sicherstellen oder ein im Image vorhandenes nutzen
test: ["CMD", "wget", "--spider", "http://localhost/health"]  # Alpine-Images haben wget

Healthcheck konfiguriert – als Nächstes konkrete Datenbank-Setups.

PostgreSQL Healthcheck – Praxis-Konfiguration

Das offizielle PostgreSQL-Image bringt ein sehr nützliches Werkzeug mit: pg_isready.

Es prüft gezielt, ob PostgreSQL bereit ist – zuverlässiger als eine selbstgeschriebene SQL-Abfrage.

Basiskonfiguration (empfohlen)

version: '3.8'

services:
  web:
    image: node:20-alpine
    depends_on:
      db:
        condition: service_healthy  # entscheidend: auf Healthcheck warten
        restart: true  # bei DB-Neustart startet die App mit neu
    environment:
      DATABASE_URL: postgresql://postgres:password@db:5432/myapp
    command: npm start

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: myapp
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 60s
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

pg_isready im Detail

pg_isready -U postgres -d myapp
  • -U: Benutzername – muss existieren
  • -d: Datenbankname (optional, aber empfohlen)

Warum -U? Ohne Angabe versucht pg_isready den aktuellen Systembenutzer – das erzeugt Warnungen in den Logs. Funktional meist unkritisch, aber unschön.

Erweiterte Konfiguration: echte Abfrage

pg_isready prüft nur Erreichbarkeit, nicht ob Abfragen wirklich funktionieren. Für strengere Prüfungen:

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres && psql -U postgres -d myapp -c 'SELECT 1'"]
  interval: 10s
  timeout: 10s  # Timeout erhöhen wegen der zusätzlichen Abfrage
  retries: 3
  start_period: 60s

SELECT 1 ist die einfachste Abfrage – wenn sie klappt, kann die Datenbank SQL verarbeiten.

Für die meisten Szenarien reicht das Basis-pg_isready.

Laufzeitverhalten

Nach der Konfiguration starten Sie so:

$ docker-compose up

Creating network "myapp_default" ... done
Creating myapp_db_1 ... done
Waiting for myapp_db_1 to be healthy... diese Zeile beachten
Creating myapp_web_1 ... done

db_1   | PostgreSQL init process complete; ready for start up.
db_1   | database system is ready to accept connections
web_1  | Server listening on port 3000 App startet erst nach DB-Bereitschaft

Sie sehen eine spürbare Pause – Docker wartet, bis db healthy ist. Das kann 30–60 Sekunden dauern, dafür startet alles ohne Fehler.

Fehlerbehebung: Container bleibt unhealthy

Bleibt der Datenbank-Container dauerhaft unhealthy, prüfen Sie die Healthcheck-Logs:

# Health-Status des Containers anzeigen
$ docker inspect --format='{{json .State.Health}}' myapp_db_1 | jq

{
  "Status": "unhealthy",
  "FailingStreak": 5,
  "Log": [
    {
      "Start": "2024-12-17T03:15:30Z",
      "End": "2024-12-17T03:15:30Z",
      "ExitCode": 1,
      "Output": "pg_isready: could not connect to server: Connection refused"
    }
  ]
}

Häufige Ursachen:

  1. start_period zu kurz: Datenbank initialisiert noch, Fehlerzähler läuft schon
  2. Falscher Benutzer- oder Datenbankname: pg_isready kann nicht verbinden
  3. PostgreSQL-Start fehlgeschlagen: Container-Logs prüfen mit docker logs myapp_db_1

MySQL Healthcheck – Praxis-Konfiguration

Für MySQL nutzen Sie mysqladmin ping – ein in MySQL enthaltenes Verwaltungstool.

Basiskonfiguration (empfohlen)

version: '3.8'

services:
  web:
    image: node:20-alpine
    depends_on:
      db:
        condition: service_healthy
        restart: true
    environment:
      DATABASE_URL: mysql://root:password@db:3306/myapp
    command: npm start

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: myapp
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-ppassword"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 60s
    volumes:
      - mysql_data:/var/lib/mysql

volumes:
  mysql_data:

mysqladmin ping im Detail

mysqladmin ping -h localhost -u root -ppassword
  • -h: Host (im Container: localhost)
  • -u: Benutzername
  • -p: Passwort (ohne Leerzeichen nach -p)

Bei laufendem MySQL liefert der Befehl:

mysqld is alive

Exit-Code 0 – Healthcheck bestanden.

Passwort korrekt handhaben

Variante 1: Passwort direkt (für Entwicklung)

healthcheck:
  test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-ppassword"]

Einfach, aber das Passwort steht fest in der Konfiguration.

Variante 2: Umgebungsvariable (empfohlen)

db:
  environment:
    MYSQL_ROOT_PASSWORD: password
  healthcheck:
    test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"]
    # Hinweis: $$ statt $

Wichtig: $$ statt $.

Warum? Docker Compose interpoliert Umgebungsvariablen vor dem Start. Mit $MYSQL_ROOT_PASSWORD sucht Compose auf dem Host – mit $$ überlässt Compose die Auflösung der Shell im Container.

Variante 3: ohne Passwort (einfach, umstritten)

healthcheck:
  test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]

Manche MySQL-Konfigurationen erlauben lokale Verbindungen ohne Passwort – am einfachsten, für Produktion nicht empfohlen.

Besonderheiten bei MySQL 8.0

MySQL 8.0 nutzt standardmäßig das Authentifizierungs-Plugin caching_sha2_password. Ältere Clients können Probleme haben. Bei Authentifizierungsfehlern können Sie das alte Plugin erzwingen:

db:
  image: mysql:8.0
  command: --default-authentication-plugin=mysql_native_password
  environment:
    MYSQL_ROOT_PASSWORD: password
    MYSQL_DATABASE: myapp
  healthcheck:
    test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-ppassword"]
    interval: 10s
    timeout: 5s
    retries: 3
    start_period: 60s

Häufige Probleme

Problem 1: Access denied for user ‘root’@‘localhost’

Falsches Passwort oder Umgebungsvariable nicht aktiv. Prüfen Sie:

  1. Schreibweise von MYSQL_ROOT_PASSWORD
  2. Passwort im healthcheck
  3. Ob $$ korrekt escapet ist

Problem 2: Container startet langsam, bleibt lange im Status starting

MySQL braucht Zeit für die Erstinitialisierung. start_period mindestens 60 s – bei großen Init-Skripten eher 120 s oder mehr.

Problem 3: Healthcheck bestanden, App verbindet trotzdem nicht

Oft Netzwerk- oder App-Konfiguration. Prüfen Sie:

  1. Connection-String der App (Hostname db, nicht localhost)
  2. Docker-Netzwerk
  3. docker network inspect – alle Container im gleichen Netzwerk?

Healthchecks für andere Datenbanken und Dienste

Mit PostgreSQL und MySQL als Vorlage sind andere Dienste schnell abgedeckt. Kurzreferenz:

Redis

redis:
  image: redis:7-alpine
  healthcheck:
    test: ["CMD", "redis-cli", "ping"]
    interval: 10s
    timeout: 3s
    retries: 3
    start_period: 15s

redis-cli ping liefert PONG, Exit-Code 0. Redis startet schnell – start_period kann kürzer sein.

MongoDB

mongo:
  image: mongo:7
  healthcheck:
    test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
    interval: 10s
    timeout: 5s
    retries: 3
    start_period: 30s

Hinweis: Ab MongoDB 6.0 heißt das Tool mongosh statt mongo. Für ältere Versionen:

test: ["CMD", "mongo", "--eval", "db.adminCommand('ping')"]

RabbitMQ

rabbitmq:
  image: rabbitmq:3-management-alpine
  healthcheck:
    test: ["CMD", "rabbitmq-diagnostics", "ping"]
    interval: 10s
    timeout: 5s
    retries: 3
    start_period: 40s

RabbitMQ startet relativ langsam – start_period mindestens 40 s.

Generische HTTP-Dienste

Bietet der Dienst einen Health-Endpunkt (/health oder /ping), nutzen Sie wget oder curl:

api:
  image: myapp:latest
  healthcheck:
    test: ["CMD", "wget", "--spider", "--quiet", "http://localhost:8080/health"]
    # oder mit curl (falls im Image vorhanden)
    # test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
    interval: 10s
    timeout: 3s
    retries: 3
    start_period: 20s

--spider prüft nur ohne Download, --quiet unterdrückt Log-Ausgabe.

Hinweis: wget oder curl muss im Image sein. Alpine hat standardmäßig wget, Debian/Ubuntu oft curl.

Dienste ohne spezielles Prüfwerkzeug

Ohne dediziertes Tool können Sie mit netcat (nc) den Port prüfen:

service:
  image: some-service:latest
  healthcheck:
    test: ["CMD-SHELL", "nc -z localhost 9000 || exit 1"]
    interval: 10s
    timeout: 3s
    retries: 3
    start_period: 30s

nc -z prüft nur, ob der Port offen ist – grober als echte Bereitschaftsprüfungen.

wait-for-it-Skripte: braucht man die noch?

Bei Recherchen zu Docker-Startreihenfolgen stoßen Sie oft auf wait-for-it oder wait-for-Skripte.

Die Idee: Im entrypoint der App wartet ein Skript, bis der TCP-Port des abhängigen Dienstes erreichbar ist.

Klassisches wait-for-it-Setup

web:
  image: node:20-alpine
  depends_on:
    - db  # nur einfaches depends_on, kein Health-Status
  volumes:
    - ./wait-for-it.sh:/wait-for-it.sh  # Skript mounten
  command: ["/wait-for-it.sh", "db:5432", "--", "npm", "start"]

wait-for-it.sh prüft db:5432 in einer Schleife und startet erst dann npm start.

Warum wird das nicht mehr empfohlen?

Best Practice 2024: Native healthcheck statt Skript, wenn möglich.

Gründe:

  1. Klarere Konfiguration: Health-Logik beim Datenbank-Service, Abhängigkeiten auf einen Blick
  2. Keine Extra-Dateien: kein Skript, kein Volume-Mount
  3. Stärkere Prüfung: healthcheck prüft echte Bereitschaft, TCP-Port-Check ist grob
  4. Bessere Wiederverwendbarkeit: healthcheck einmal konfiguriert, alle abhängigen Services profitieren

Ein oft zitiertes Community-Artikel-Titel lautet sinngemäß: „Forget wait-for-it, use docker-compose healthcheck and depends_on instead”.

Wann wait-for-it trotzdem sinnvoll ist

Fall 1: Image oder Compose nicht änderbar

Third-Party-Image ohne healthcheck und ohne Schreibrechte – dann ist wait-for-it auf App-Seite oft die einzige Option.

Fall 2: Warten auf mehrere Dienste

./wait-for-it.sh db:5432 redis:6379 rabbitmq:5672 -- npm start

depends_on unterstützt mehrere Abhängigkeiten, aber wait-for-it ist in einem Befehl kompakter. Selten nötig.

Weitere Alternativen

Neben wait-for-it gibt es:

  • dockerize: Go-basiert, mehr Features, Umgebungsvariablen-Templates
  • wait-for: vereinfachte Shell-Variante von wait-for-it
  • docker-compose-wait: Python, unterstützt HTTP-Checks

Ehrlich gesagt: Wenn healthcheck möglich ist, lohnt sich der Aufwand für diese Tools selten.

Checkliste zur Fehlerbehebung und Best Practices

Konfiguration steht, es klappt trotzdem nicht? Mit dieser Checkliste lösen Sie rund 90 % der Fälle.

Schnelle Diagnose-Befehle

# 1. Status aller Container
$ docker-compose ps

NAME       COMMAND    SERVICE   STATUS              PORTS
myapp_db   postgres   db        healthy             5432/tcp
myapp_web  npm start  web       running             0.0.0.0:3000->3000/tcp

# 2. Healthcheck-Details
$ docker inspect --format='{{json .State.Health}}' myapp_db_1 | jq

# 3. Container-Logs
$ docker-compose logs db
$ docker-compose logs web

# 4. Logs live verfolgen
$ docker-compose logs -f --tail=100

Entscheidungsbaum für häufige Probleme

Problem: Container bleibt starting, wird nie healthy

  1. start_period zu kurz? → auf 60 s erhöhen
  2. Healthcheck-Befehl korrekt? → mit docker inspect prüfen
  3. Befehl manuell im Container testen → docker exec -it myapp_db_1 pg_isready -U postgres

Problem: unhealthy, dann wieder healthy – hin und her

  1. interval zu kurz bei knappen Ressourcen → 10 s oder 15 s
  2. retries zu niedrig → auf 5 erhöhen
  3. echte DB-Performance-Probleme → DB-Logs prüfen

Problem: Healthcheck OK, App verbindet trotzdem nicht

  1. Connection-String: Hostname = Service-Name (db), nicht localhost
  2. Port: Container-zu-Container = interner Port (5432), nicht gemappten Host-Port
  3. Netzwerk: alle Services im gleichen network

Best Practices für Produktion

1. Empfohlene Parameter (konservativ)

healthcheck:
  interval: 10s          # Balance zwischen Reaktionszeit und Ressourcen
  timeout: 5s            # genug Zeit für den Befehl
  retries: 5             # toleriert sporadische Fehler
  start_period: 60s      # ausreichend DB-Startzeit

Stabil in den meisten Szenarien. Bei großen Init-Skripten start_period bis 120 s.

2. restart-Strategie

web:
  depends_on:
    db:
      condition: service_healthy
      restart: true  # bei DB-Neustart startet die App mit neu
  restart: unless-stopped  # Container nach Exit automatisch neu starten

restart: true sorgt dafür, dass abhängige Services nach DB-Upgrade oder -Neustart neu verbinden.

3. Ressourcenlimits

Healthchecks verbrauchen wenig, aber etwas. Bei knappen Ressourcen:

healthcheck:
  interval: 30s  # seltener prüfen
  timeout: 3s    # kürzerer Timeout

In der Praxis ist der Overhead meist vernachlässigbar – außer bei Hunderten von Containern.

4. Health-Status überwachen

In Produktion lohnt Monitoring. Docker-Health-Events lassen sich von Prometheus, Grafana usw. erfassen.

# docker-compose.yml
services:
  db:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 60s
    labels:
      - "prometheus.io/scrape=true"  # Prometheus kann Health-Status scrapen

5. Mehrere Umgebungen

Entwicklung und Produktion können unterschiedlich konfiguriert sein:

# docker-compose.yml (Entwicklung)
db:
  healthcheck:
    start_period: 30s  # wenig Daten, schneller Start

# docker-compose.prod.yml (Produktion)
db:
  healthcheck:
    start_period: 120s  # viele Daten, langsamer Start
    interval: 5s        # häufigere Prüfung

Start mit mehreren Dateien:

docker-compose -f docker-compose.yml -f docker-compose.prod.yml up

Debug-Tipps

Tipp 1: Healthcheck-Befehl manuell testen

Container betreten und Befehl ausführen:

$ docker exec -it myapp_db_1 sh
/# pg_isready -U postgres -d myapp
/var/run/postgresql:5432 - accepting connections
/# echo $?
0 Exit-Code 0 = Erfolg

Tipp 2: Healthcheck vorübergehend deaktivieren

Beim Debuggen healthcheck auskommentieren, einfaches depends_on nutzen:

web:
  depends_on:
    - db  # vorübergehend einfacher Modus
    # db:
    #   condition: service_healthy

Verbindung klappt? Dann healthcheck wieder aktivieren und dort weiter suchen.

Tipp 3: Docker-Ereignisprotokoll

Docker protokolliert alle Container-Ereignisse inklusive Health-Status:

$ docker events --filter 'event=health_status'

2024-12-17T03:15:30.123456789Z container health_status: healthy (name=myapp_db_1)
2024-12-17T03:16:45.987654321Z container health_status: unhealthy (name=myapp_db_1)

So sehen Sie, wann unhealthy wurde – kombiniert mit Log-Zeitstempeln zur Ursachenanalyse.

Fazit

Zurück zur Eingangsfrage: Warum reicht depends_on nicht?

Kurz gesagt: Es reicht nicht aus.

depends_on wartet standardmäßig nur auf Container-Start, nicht auf Service-Bereitschaft. Ein laufender DB-Container heißt noch nicht „Verbindungen möglich” – genau in dieser Lücke landen die meisten Fehler.

Die Lösung ist straightforward:

  1. Datenbank mit healthcheck – pg_isready oder mysqladmin ping prüft echten Bereitschaftsstatus
  2. condition: service_healthy – App wartet auf bestandenen Healthcheck
  3. Angemessener start_period – mindestens 60 s für DB-Initialisierung

Damit verschwinden die meisten Startfehler.

Schnelle nächste Schritte:

  • Sofort: PostgreSQL- oder MySQL-Block aus dem Artikel kopieren, Umgebungsvariablen anpassen
  • Heute: depends_on im Projekt auf service_healthy umstellen
  • Team: Konfiguration teilen und Standards vereinheitlichen

Falls Ihr Fall hier nicht vorkommt – gerne in den Kommentaren melden. Bei Docker Compose gibt es noch viele Stolpersteine; gemeinsam lassen sie sich abarbeiten.

Wenn dieser Artikel Ihr langjähriges Startproblem gelöst hat, freue ich mich über Feedback. Genau solche „Endlich läuft es”-Momente sind der Grund, warum ich das aufgeschrieben habe.

Vollständiger Workflow für Docker Compose Healthcheck-Konfiguration

Lösung für Anwendungsstartfehler, wenn die Datenbank noch nicht bereit ist – inklusive vollständiger PostgreSQL- und MySQL-Konfigurationsvorlagen

⏱️ Estimated time: 30 min

  1. 1

    Step 1: Ursache verstehen: Grenzen von depends_on

    Ursache:
    • Docker depends_on steuert nur die Container-Startreihenfolge, nicht ob der Dienst wirklich bereit ist
    • Compose wartet nur, bis der Container läuft – nicht, bis der Dienst tatsächlich nutzbar ist
    • Docker-Dokumentation: Compose does not wait until a container is „ready“, only until it's running
    • Bedeutung: Compose wartet nur, bis der Container läuft – nicht, bis der Dienst tatsächlich nutzbar ist

    Zeitlücke zwischen Container-Start und Service-Bereitschaft:
    • 0 s: Docker startet den Container, postgres-Prozess startet (depends_on gibt hier schon frei)
    • 2 s: Initialisierung des Datenverzeichnisses
    • 5 s: Laden der Konfigurationsdateien
    • 8 s: Ausführung von Init-Skripten (falls vorhanden)
    • 12 s: endlich bereit, Verbindungen werden akzeptiert

    Dazwischen liegt eine Lücke von rund 12 Sekunden. Versucht Ihre Web-App in Sekunde 1 eine Verbindung zur Datenbank, ist das Ergebnis zwangsläufig Connection refused.
  2. 2

    Step 2: Drei condition-Konfigurationen für depends_on

    Drei condition-Konfigurationen für depends_on:

    1. service_started (Standard)
    • Wartet nur auf Container-Start, nicht auf Service-Bereitschaft
    • Nicht empfohlen

    2. service_healthy (empfohlen)
    • Wartet auf Container-Start und bestandenen Healthcheck
    • Stellt sicher, dass der Dienst wirklich bereit ist

    3. service_completed_successfully
    • Wartet, bis der Container erfolgreich beendet wurde
    • Geeignet für Einmal-Aufgaben

    Empfehlung service_healthy:
    • In docker-compose.yml konfigurieren: depends_on: db: condition: service_healthy
    • Stellt sicher, dass die Anwendung erst startet, wenn die Datenbank wirklich bereit ist
  3. 3

    Step 3: Korrekte healthcheck-Konfiguration für PostgreSQL und MySQL

    PostgreSQL Healthcheck:
    • pg_isready prüft, ob die Datenbank bereit ist
    • Konfigurationsbeispiel:
    healthcheck:
    test: ["CMD-SHELL", "pg_isready -U postgres"]
    interval: 5s
    timeout: 5s
    retries: 5
    start_period: 60s (genug Zeit für die Initialisierung)

    MySQL Healthcheck:
    • mysqladmin ping prüft, ob die Datenbank bereit ist
    • Konfigurationsbeispiel:
    healthcheck:
    test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$MYSQL_ROOT_PASSWORD"]
    interval: 5s
    timeout: 5s
    retries: 5
    start_period: 60s

    Vollständiges Beispiel:
    • healthcheck in docker-compose.yml konfigurieren (Prüfbefehl, Intervall, Timeout, Wiederholungen)
    • condition: service_healthy in depends_on verwenden
    • Stellt sicher, dass die Anwendung erst startet, wenn die Datenbank wirklich bereit ist
  4. 4

    Step 4: Fehlerbehebung und Best Practices

    Checkliste zur Fehlerbehebung:
    1. Prüfen, ob der Healthcheck-Befehl korrekt ist
    • Healthcheck-Befehl manuell im Container ausführen und Exit-Code 0 bestätigen
    • Beispiel: docker exec -it db_container pg_isready -U postgres

    2. Healthcheck vorübergehend deaktivieren
    • Beim Debuggen healthcheck auskommentieren
    • Einfaches depends_on verwenden, um Healthcheck-Probleme auszuschließen

    3. Docker-Ereignisprotokoll prüfen
    • docker events zeigt Container-Start- und Healthcheck-Ereignisse

    Best Practices:
    1. Datenbank mit healthcheck ausstatten – pg_isready oder mysqladmin ping prüft den tatsächlichen Bereitschaftsstatus
    2. condition: service_healthy verwenden, damit die Anwendung auf bestandenen Healthcheck wartet
    3. Angemessenen start_period setzen (mindestens 60 s), damit die Datenbank genug Initialisierungszeit hat

    Mit diesen drei Maßnahmen verschwinden die meisten Container-Startprobleme.

    Moderne Alternative zu wait-for-it-Skripten:
    • depends_on + healthcheck kombinieren
    • Keine zusätzlichen Skripte nötig, einfache Konfiguration, hohe Zuverlässigkeit
    • Geeignet für alle Docker Compose-Projekte

FAQ

Warum reicht depends_on nicht aus? Was ist der Unterschied zwischen Container-Start und Service-Bereitschaft?
Ursache: Docker depends_on steuert nur die Container-Startreihenfolge, nicht ob der Dienst wirklich bereit ist. Compose wartet nur, bis der Container läuft – nicht, bis der Dienst tatsächlich nutzbar ist.

In der Docker-Dokumentation steht ein oft übersehener Satz: Compose does not wait until a container is „ready“, only until it's running. Bedeutung: Compose wartet nur, bis der Container läuft – nicht, bis der Dienst tatsächlich nutzbar ist.

Zeitlücke zwischen Container-Start und Service-Bereitschaft – der Startvorgang eines PostgreSQL-Containers:
• 0 s: Docker startet den Container, postgres-Prozess startet (depends_on gibt hier schon frei)
• 2 s: Initialisierung des Datenverzeichnisses
• 5 s: Laden der Konfigurationsdateien
• 8 s: Ausführung von Init-Skripten (falls vorhanden)
• 12 s: endlich bereit, Verbindungen werden akzeptiert

Dazwischen liegt eine Lücke von rund 12 Sekunden. Versucht Ihre Web-App in Sekunde 1 eine Verbindung zur Datenbank, ist das Ergebnis zwangsläufig Connection refused.
Welche drei condition-Konfigurationen gibt es für depends_on?
Drei condition-Konfigurationen für depends_on:

1) service_started (Standard):
• Wartet nur auf Container-Start, nicht auf Service-Bereitschaft
• Nicht empfohlen

2) service_healthy (empfohlen):
• Wartet auf Container-Start und bestandenen Healthcheck
• Stellt sicher, dass der Dienst wirklich bereit ist
• In docker-compose.yml: depends_on: db: condition: service_healthy

3) service_completed_successfully:
• Wartet, bis der Container erfolgreich beendet wurde
• Geeignet für Einmal-Aufgaben

Empfehlung service_healthy: Stellt sicher, dass die Anwendung erst startet, wenn die Datenbank wirklich bereit ist.
Wie konfiguriert man Healthchecks für PostgreSQL und MySQL?
PostgreSQL Healthcheck:
• pg_isready prüft, ob die Datenbank bereit ist
• Konfigurationsbeispiel:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
start_period: 60s (genug Zeit für die Initialisierung)

MySQL Healthcheck:
• mysqladmin ping prüft, ob die Datenbank bereit ist
• Konfigurationsbeispiel:
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$MYSQL_ROOT_PASSWORD"]
interval: 5s
timeout: 5s
retries: 5
start_period: 60s

Vollständiges Beispiel: healthcheck in docker-compose.yml konfigurieren (Prüfbefehl, Intervall, Timeout, Wiederholungen), condition: service_healthy in depends_on verwenden – so startet die Anwendung erst, wenn die Datenbank wirklich bereit ist.
Wie behebt man Probleme mit Healthchecks?
Checkliste zur Fehlerbehebung:

1) Prüfen, ob der Healthcheck-Befehl korrekt ist:
• Healthcheck-Befehl manuell im Container ausführen und Exit-Code 0 bestätigen
• Beispiel: docker exec -it db_container pg_isready -U postgres
• Exit-Code 0 bedeutet Erfolg

2) Healthcheck vorübergehend deaktivieren:
• Beim Debuggen healthcheck auskommentieren
• Einfaches depends_on verwenden, um Healthcheck-Probleme auszuschließen
• Nach erfolgreicher Verbindung healthcheck wieder aktivieren

3) Docker-Ereignisprotokoll prüfen:
• docker events zeigt Container-Start- und Healthcheck-Ereignisse
Was sind Best Practices für Healthcheck-Konfiguration?
Best Practices:
1) Datenbank mit healthcheck ausstatten – pg_isready oder mysqladmin ping prüft den tatsächlichen Bereitschaftsstatus
2) condition: service_healthy verwenden, damit die Anwendung auf bestandenen Healthcheck wartet
3) Angemessenen start_period setzen (mindestens 60 s), damit die Datenbank genug Initialisierungszeit hat

Mit diesen drei Maßnahmen verschwinden die meisten Container-Startprobleme.

Moderne Alternative zu wait-for-it-Skripten:
• depends_on + healthcheck kombinieren
• Keine zusätzlichen Skripte nötig, einfache Konfiguration, hohe Zuverlässigkeit
• Geeignet für alle Docker Compose-Projekte

Schnelle Handlungsempfehlungen:
• Sofort umsetzbar: PostgreSQL- oder MySQL-Konfiguration aus dem Artikel kopieren und Umgebungsvariablen anpassen
• Heute Abend: depends_on im Team-Projekt auf service_healthy upgraden
• Nächstes Team-Meeting: Mit Kollegen teilen und Konfigurationsstandards vereinheitlichen

13 Min. Lesezeit · Veröffentlicht am: 17. Dez. 2025 · Aktualisiert am: 14. Juli 2026

Ähnliche Beiträge

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog