Design wechseln

Docker Compose in Produktion: Healthchecks, Restart-Strategien und Log-Verwaltung

Easton editorial illustration: observability control panel

Server-Alarme häufen sich auf dem Handy. Im Terminal: Festplatte bei 99 % – Container-Logs belegen 50 GB.

Das war noch nicht das Schlimmste. Letztes Jahr zeigte ein API-Container running, die Datenbankverbindung war aber längst weg – alle Requests lieferten 500. Drei Stunden Fehlersuche. Laut Last9-Studie kostet ein Container-Zombie-Zustand im Schnitt 3,2 Stunden Troubleshooting.

Viele Teams deployen Docker Compose in Produktion zum ersten Mal mit Port-Mapping und Volume-Mount – fertig. Kein Healthcheck, keine Log-Rotation, Restart-Strategie oft nur restart: always. Ergebnis: Container wirken aktiv, sind aber tot; Logs wachsen unkontrolliert; abstürzende Services starten endlos neu und fressen CPU und RAM.

Dieser Artikel erklärt die drei Kernkonfigurationen für die Produktion – Healthcheck, Restart-Strategie und Log-Verwaltung – mit Beispielen, Healthcheck-Befehlen für gängige Services, Troubleshooting-Schritten und einer kopierfertigen docker-compose.yml-Vorlage.

Healthcheck – damit Container wirklich leben

Status running heißt nicht, dass die Anwendung arbeitet. Keine DB-Verbindung, Port nicht offen, Prozess hängt – Docker merkt das nicht. Der Healthcheck ist ein Herzschlag-Monitor, der regelmäßig prüft, ob die App noch antwortet.

Konfigurationssyntax

In docker-compose.yml sieht ein healthcheck so aus:

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres"]
  interval: 10s      # alle 10 Sekunden prüfen
  timeout: 5s        # maximal 5 Sekunden pro Prüfung warten
  retries: 5         # erst nach 5 Fehlversuchen unhealthy
  start_period: 30s  # 30 Sekunden Warm-up nach Start

Die Parameter müssen zusammenpassen. timeout darf nicht größer als interval sein, sonst überlappen sich Prüfungen. start_period nicht weglassen – Datenbanken brauchen Startzeit; zu kurz, und der Check markiert den Container fälschlich als tot.

Healthcheck-Befehle für gängige Services

Je nach Service unterschiedliche Checks:

PostgreSQL

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres -d mydb"]
  interval: 10s
  timeout: 5s
  retries: 5
  start_period: 30s

pg_isready ist das PostgreSQL-eigene Tool, ob die DB Verbindungen annimmt.

MySQL / MariaDB

healthcheck:
  test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"]
  interval: 10s
  timeout: 5s
  retries: 5
  start_period: 30s

Passwort mit $$ escapen, sonst interpretiert YAML $ als Variable.

Redis

healthcheck:
  test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
  interval: 10s
  timeout: 3s
  retries: 3

Redis ping liefert PONG – mit grep das Ergebnis absichern.

Webserver (HTTP-Check)

healthcheck:
  test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 10s

-f sorgt dafür, dass curl bei HTTP-Status ≠ 2xx mit Fehlercode endet und der Healthcheck fehlschlägt.

Fallstrick: In schlanken Images wie Alpine fehlt oft curl. Entweder installieren (apk add curl) oder wget nutzen:

test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1"]

Startreihenfolge steuern

Die API startet, bevor die Datenbank bereit ist – Verbindungsfehler, Crash. Das kennen viele. depends_on mit condition: service_healthy hilft:

services:
  postgres:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  api:
    build: ./api
    depends_on:
      postgres:
        condition: service_healthy  # API erst nach healthy-Postgres

Docker Compose startet api erst, wenn postgres healthy ist – kein „DB noch nicht da, API verbindet schon“.

Restart-Strategie – kontrollierte Wiederherstellung nach Fehlern

Container abgestürzt – automatisch neu starten klingt gut. Ist die Ursache nicht behoben, entsteht eine Endlosschleife: CPU, RAM und echte Fehler verschwinden im Rauschen.

Konfigurationssyntax

Restart-Policy im deploy-Block:

deploy:
  restart_policy:
    condition: on-failure   # nur bei Fehler neu starten
    delay: 5s               # 5 Sekunden warten vor Neustart
    max_attempts: 3         # maximal 3 Neustartversuche
    window: 120s            # innerhalb 120s stabil = erfolgreiche Wiederherstellung

condition hat drei Werte:

  • none: kein Neustart
  • on-failure: nur bei abnormalen Exits (Exit-Code ≠ 0)
  • any: immer neu starten

Empfehlung für Produktion

on-failure statt always.

Warum? restart: always startet in jedem Fall neu – Bug, DB down, falsche Config. Crash-Loop, Log-Flut, CPU-Verbrauch.

Mit on-failure und max_attempts stoppt es nach wenigen Versuchen. Ops sieht den finalen Ausfall und kann die Ursache beheben.

Parameter feintunen

delay: Pause vor Neustart. Zu kurz – Container nicht sauber beendet; zu lang – längere Ausfallzeit. 5–10 Sekunden sind ein guter Start.

window wird oft übersehen: Wie lange muss der Container nach Neustart stabil laufen, damit der Versuch zählt? Bei window: 120s und erneutem Crash innerhalb von 120s wird max_attempts nicht zurückgesetzt – kein falscher Erfolg nach einer Sekunde Laufzeit.

Zusammenspiel mit Healthcheck

Healthcheck und Restart-Policy arbeiten zusammen:

  1. Healthcheck scheitert retries-mal → Container unhealthy
  2. Mit restart_policy versucht Docker einen Neustart
  3. Nach Neustart zählt der Healthcheck von vorn
  4. Besteht der Check – normal; scheitert er wieder – weiter bis max_attempts erschöpft

So gibt es automatische Wiederherstellung mit Begrenzung gegen Endlos-Restarts.

Log-Verwaltung – Festplatte nicht zum Platzen bringen

Der Alarm um drei Uhr morgens: 99 % Festplatte, 50 GB Logs – mehrfach erlebt. Der Standard-Treiber json-file rotiert nicht; ohne Limits wächst alles unbegrenzt.

Log-Rotation konfigurieren

In docker-compose.yml logging setzen:

logging:
  driver: "json-file"
  options:
    max-size: "10m"      # max. 10 MB pro Logdatei
    max-file: "3"        # höchstens 3 Dateien behalten
    compress: "true"     # alte Logs komprimieren

Maximal ~30 MB pro Container (10 MB × 3). Ab 10 MB neue Datei; ab vier Dateien wird die älteste gelöscht oder komprimiert.

Logs liegen unter /var/lib/docker/containers/<container-id>/<container-id>-json.log. Mit du prüfen:

du -sh /var/lib/docker/containers/*/*-json.log

Treiberwahl

Docker bietet json-file, syslog, fluentd, journald, local u. a. Für die meisten Fälle reichen json-file oder local.

Laut Docker-Dokumentation ist local effizienter als json-file und rotiert ohne manuelles max-size/max-file. Bei sehr hohem Logvolumen (zig GB pro Tag) local erwägen:

logging:
  driver: "local"

Nachteil: docker logs funktioniert nicht direkt – ggf. mode: "non-blocking" in den Optionen.

Zentralisierte Log-Sammlung (optional)

Auf einem Host reichen json-file oder local. Bei vielen Servern und Containern hilft Zentralisierung:

  • Fluentd: leichtgewichtig, kleine Cluster
  • ELK Stack (Elasticsearch + Logstash + Kibana): mächtig, aufwändiger
  • Loki + Grafana: cloud-nativ, gut zu Prometheus

Details zu Fluentd-Konfiguration – Kurzüberblick:

logging:
  driver: "fluentd"
  options:
    fluentd-address: "localhost:24224"
    tag: "docker.{{.Name}}"

Fluentd leitet Logs an die konfigurierte Adresse weiter.

Vollständige Konfigurationsvorlage

Healthcheck, Restart-Policy und Logging zusammen – produktionsreife docker-compose.yml mit PostgreSQL, Redis und API:

version: '3.8'

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myuser -d mydb"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    deploy:
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
        compress: "true"

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 5s
    deploy:
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  api:
    build:
      context: ./api
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      DATABASE_URL: postgres://myuser:mypassword@postgres:5432/mydb
      REDIS_URL: redis://redis:6379
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s
    deploy:
      restart_policy:
        condition: on-failure
        delay: 10s
        max_attempts: 3
        window: 120s
    logging:
      driver: "json-file"
      options:
        max-size: "50m"
        max-file: "5"
        compress: "true"

volumes:
  postgres_data:

Wichtige Punkte der Vorlage

Startreihenfolge: depends_on der API wartet auf healthy postgres und redis – keine Startfehler durch frühe Verbindungsversuche.

Loggrößen: postgres/redis meist 10 MB × 3; API oft mehr – 50 MB × 5. An echte Logmenge anpassen.

Restart-delay: postgres braucht nach Neustart Zeit – delay 5s; API delay 10s als Puffer für Healthchecks.

start_period: postgres 30s für DB-Init; redis 5s; API 10s für App-Start.

Vorlage kopieren, Umgebungsvariablen und Images anpassen. Weitere Services (MongoDB, MinIO) nach demselben Muster mit Healthcheck, Restart und Logging.

Häufige Fallstricke und Troubleshooting

Nach dem Deploy tauchen trotzdem Probleme auf – typische Fälle und Schritte.

Healthcheck schlägt dauerhaft fehl

Symptom: Status bleibt unhealthy, die App wirkt normal.

Schritte:

  1. Tools im Check vorhanden?

    docker exec <container> which curl
    docker exec <container> which pg_isready

    Alpine oft ohne curl – installieren oder wget.

  2. Check-Befehl manuell:

    docker exec <container> curl -f http://localhost:8080/health

    Fehler deuten auf den Health-Endpoint selbst.

  3. Detaillierter Health-Status:

    docker inspect --format='{{json .State.Health}}' <container> | jq

    Letzte Ergebnisse, Fehlerursachen, Zeitstempel.

Container startet ständig neu

Symptom: Start, kurzer Lauf, wieder down – Logs voller Restart-Einträge.

Schritte:

  1. Exit-Grund:

    docker inspect --format='{{.State.ExitCode}}' <container>
    docker inspect --format='{{.State.Error}}' <container>

    Exit-Codes: 1 = allgemeiner Fehler, 137 = OOM, 139 = Segfault.

  2. Restart-Zähler:

    docker inspect --format='{{.RestartCount}}' <container>

    Hohe Zahl – prüfen, ob max_attempts greift.

  3. Logs:

    docker logs --tail 100 <container>

Festplatte durch Logs voll

Symptom: Speicheralarm, /var/lib/docker/containers sehr groß.

Schritte:

  1. Größte Logdateien:

    du -sh /var/lib/docker/containers/*/*-json.log | sort -rh | head -5
  2. Rotation aktiv?

    docker inspect --format='{{.HostConfig.LogConfig}}' <container>

    Config: {} = keine Rotation konfiguriert.

  3. Logs manuell leeren (Notlösung):

    truncate -s 0 /var/lib/docker/containers/<id>/<id>-json.log

    Langfristig Rotation in compose setzen.

Schnell-Befehle für die Fehlersuche

# Health-Status aller Container
docker ps --format "table {{.Names}}\t{{.Status}}"

# Healthcheck-Historie eines Containers
docker inspect --format='{{json .State.Health}}' <container>

# Exit-Code und Restart-Anzahl
docker inspect --format='ExitCode: {{.State.ExitCode}}, RestartCount: {{.RestartCount}}' <container>

# Logdateigrößen
du -sh /var/lib/docker/containers/*/*-json.log | sort -rh

# Letzte 100 Logzeilen
docker logs --tail 100 <container>

Fazit

In Produktion sind diese drei Konfigurationen Pflicht, nicht optional: Healthcheck zeigt, ob der Container wirklich lebt; Restart-Strategie ermöglicht Wiederherstellung mit Limit gegen Endlos-Loops; Log-Verwaltung schützt die Festplatte.

3,2 Stunden
durchschnittliche Troubleshooting-Zeit bei Container-Zombie-Zuständen

Kern-Konfigurationsliste:

  • Healthcheck: test + interval + timeout + retries + start_period
  • Restart-Strategie: condition: on-failure + max_attempts: 3
  • Log-Rotation: max-size: 10m + max-file: 3 + compress: true

Drei Schritte zum Handeln:

  1. Bestehende docker-compose.yml prüfen – fehlen die drei Blöcke, mindestens Healthcheck und Log-Rotation ergänzen.
  2. Mit der Vorlage einen Test-Service deployen – Healthcheck und Rotation beobachten.
  3. Troubleshooting-Befehle griffbereit halten – beim nächsten Nacht-Alarm schneller reagieren.

Lassen Sie Container in Produktion nicht ungeschützt laufen. Mit diesen drei Schutzebenen gibt es Auto-Recovery, schnellere Diagnose – und keine volllaufende Festplatte durch Logs.

FAQ

Wie setzt man interval und timeout für Healthchecks sinnvoll?
interval: 10–30 Sekunden, timeout: 3–10 Sekunden. Entscheidend: timeout darf nicht größer als interval sein, sonst startet die nächste Prüfung, bevor die vorherige endet. Bei Datenbanken start_period 30–60 Sekunden einplanen, damit die Initialisierung Zeit hat.
restart: always oder on-failure – was ist besser?
In der Produktion empfiehlt sich on-failure mit max_attempts. always startet in jedem Fall neu – auch bei Konfigurationsfehlern oder Code-Bugs – und erzeugt Crash-Loops. on-failure startet nur bei abnormalen Exits neu; max_attempts begrenzt die Versuche, damit Ops das Problem erkennt.
Wie groß sollten Logdateien sein und wie viele behalten?
Typische Services: max-size: 10m + max-file: 3, insgesamt 30 MB. Logintensive Services (z. B. APIs): 50m × 5. An die tatsächliche Logmenge anpassen und compress: true aktivieren, um Speicher zu sparen.
Healthcheck schlägt dauerhaft fehl, die App läuft aber normal – was tun?
Prüfen, ob die Tools im Check existieren (curl, pg_isready usw.) – Alpine-Images fehlen oft. Den Check-Befehl manuell ausführen und die Ausgabe lesen. Mit docker inspect die Healthcheck-Historie ansehen, um die Fehlerursache zu finden.
Wozu dient depends_on mit condition: service_healthy?
Der abhängige Container startet erst, wenn der Healthcheck des Dependency-Services bestanden ist. Zuverlässiger als ein einfaches depends_on – verhindert, dass die API startet, bevor die Datenbank bereit ist. Der abhängige Service braucht einen eigenen healthcheck.
Wie prüft man schnell, wie viel Speicher Container-Logs belegen?
Befehl: du -sh /var/lib/docker/containers/*/*-json.log | sort -rh. Ist ein einzelnes Log ungewöhnlich groß, prüfen, ob die Log-Rotation für diesen Container greift.

8 Min. Lesezeit · Veröffentlicht am: 12. Apr. 2026 · Aktualisiert am: 14. Juli 2026

Ähnliche Beiträge

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog