Design wechseln

Docker Compose in Produktion: Healthcheck, Restart-Policy und Ressourcenlimits

Easton editorial illustration: input-process-output transport line

Ein Server-Alarm weckt Sie mitten in der Nacht.

Am Rechner zeigt der Container-Status running – der grüne Punkt wirkt gesund. Doch der Service? 502 Bad Gateway.

Der Datenbank-Container ist noch nicht bereit, aber der Anwendungs-Container verbindet sich schon. Verbindung fehlgeschlagen, Service down. Der Container läuft noch – der Dienst ist längst tot.

So sieht der Alltag aus, wenn man Docker Compose zum ersten Mal in Produktion einsetzt.

Läuft und läuft stabil sind zwei verschiedene Dinge. Dass docker-compose up alles startet, heißt nicht, dass es um drei Uhr morgens bei Speicherexplosion oder Prozessabsturz noch steht.

Die drei Säulen für Docker Compose in Produktion – Healthcheck, Restart-Policy und Ressourcenlimits – schließen genau diese Lücke. Dieser Artikel fasst die Konfiguration aus der Praxis zusammen, inklusive kopierbarer YAML-Vorlage – damit Container nicht nur laufen, sondern stabil laufen.

1. Healthcheck: Ist der Container wirklich einsatzbereit?

Der Status running in docker ps bedeutet nur: Der Container-Prozess lebt noch. Ob er Dienste zuverlässig bereitstellt? Unklar.

Der Healthcheck lässt Docker Ihren Container regelmäßig prüfen: HTTP-Request senden, Datenbank anpingen oder ein Skript ausführen – und so feststellen, ob der Service wirklich lebt.

Wie funktioniert der Healthcheck?

Docker sendet in dem von Ihnen gesetzten Intervall einen Prüfbefehl in den Container. Rückgabewert 0 bedeutet gesund, alles andere unhealthy. Nach mehreren Fehlversuchen wird der Container als unhealthy markiert.

Entscheidend: Ein fehlgeschlagener Healthcheck löst keinen Neustart aus. Er macht den Zustand nur sichtbar. Für automatische Wiederherstellung brauchen Sie depends_on mit Bedingungen und eine Restart-Policy.

Vier Parameter, die Sie kennen sollten

healthcheck:
  test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
  interval: 30s      # Wie oft prüfen
  timeout: 10s       # Timeout pro Prüfung
  retries: 3         # Fehlversuche bis unhealthy
  start_period: 60s  # Toleranzphase – Fehler zählen nicht zu retries

Den Parameter start_period habe ich anfangs ignoriert – mit folgendem Ergebnis: Die App startet langsam, die DB-Verbindung braucht 40 Sekunden, aber der Healthcheck beginnt nach 10 Sekunden. Drei Fehlversuche – sofort unhealthy. Mit start_period: 60s bleibt genug Zeit für die Initialisierung.

Häufige Healthcheck-Befehle

Web-Service:

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

PostgreSQL:

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

Redis:

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

Mit depends_on abhängige Services bedingt starten

Hier wird der Healthcheck am nützlichsten: Der abhängige Service startet erst, wenn die Abhängigkeit wirklich bereit ist.

services:
  app:
    depends_on:
      db:
        condition: service_healthy  # Warten bis DB-Healthcheck bestanden
      redis:
        condition: service_healthy  # Warten bis Redis-Healthcheck bestanden

Früher nutzte ich depends_on: [db, redis] – die App startete, während die Datenbank noch initialisierte. Verbindung fehlgeschlagen, sofortiger Exit. Mit condition: service_healthy wartet die App, bis die DB auf pg_isready antwortet. Ruhe.

2. Restart-Policy: Self-Healing für Container

Container down – wer richtet ihn auf?

Manuell docker restart? Probieren Sie das um drei Uhr nachts bei einem Alarm.

Die Restart-Policy überlässt das dem Docker-Daemon. Beim Container-Exit entscheidet Docker automatisch, ob neu gestartet wird.

Vier Policies im Vergleich

PolicyVerhaltenEinsatz
noKein Neustart nach ExitTemporäre Tests, CI/CD
alwaysNeustart bei jedem ExitKerndienste
on-failureNeustart nur bei abnormalen ExitsTask-Container
unless-stoppedImmer neu starten, außer manuell gestopptProduktion – Standard

Produktion: unless-stopped

restart: unless-stopped

Warum unless-stopped statt always?

Der Unterschied liegt im Verhalten nach manuellem docker stop:

  • always: Nach manuellem Stop startet der Container bei System- oder Docker-Neustart wieder
  • unless-stopped: Nach manuellem Stop bleibt er gestoppt – kein unerwartetes Wiederaufwachen

Stellen Sie sich vor: Sie stoppen einen Container für Wartung, und nach Server-Neustart läuft er von selbst wieder. Das will niemand.

on-failure mit Retry-Limit

on-failure erlaubt eine Begrenzung der Neustarts:

restart: on-failure:5  # Maximal 5 Neustarts

Schlägt der Start fünfmal hintereinander fehl, gibt Docker auf. Sinnvoll, wenn externe Ursachen (DB nicht erreichbar, Konfigurationsfehler) zu wiederholten Abstürzen führen – so vermeiden Sie Endlos-Neustart-Schleifen.

Kurz zur Auswahl

  • Kerndienste (Web, API, Datenbank): unless-stopped
  • Hintergrundjobs, Cron-Skripte: on-failure
  • Entwicklung, temporäre Läufe: no

Ein Fallstrick: Die Restart-Policy regelt nur, ob nach Container-Exit neu gestartet wird – nicht, ob der Dienst wirklich einsatzbereit ist. Dafür brauchen Sie den Healthcheck.

3. Ressourcenlimits (deploy.resources): Container unter Kontrolle halten

Kennen Sie das? Ein Container mit Speicherleck frisst den gesamten Server-Speicher – der OOM Killer beendet alle anderen Container.

Ich kenne es. Unangenehm.

Ressourcenlimits setzen pro Container eine Obergrenze: Wird sie überschritten, wird der Container beendet – andere Dienste bleiben geschützt.

limits vs. reservations

deploy:
  resources:
    limits:
      cpus: '1.0'      # Maximal 1 CPU
      memory: 512M     # Maximal 512 MB Speicher
    reservations:
      cpus: '0.5'      # Mindestens 0,5 CPU reserviert
      memory: 256M     # Mindestens 256 MB reserviert
  • limits: harte Grenze – Überschreitung beendet den Prozess (OOM)
  • reservations: weiche Grenze – teilt dem Scheduler das Mindest-Ressourcenbudget mit

Kurz gesagt: limits = „nicht mehr als“, reservations = „mindestens garantiert“.

CPU-Limits setzen

cpus: '1.0'   # Maximal 1 CPU-Kern
cpus: '0.5'   # Maximal 50 % CPU
cpus: '2.0'   # Maximal 2 Kerne

CPU-Limits sind weich – bei Überschreitung wird gedrosselt, nicht beendet. Lieber etwas großzügig setzen.

Speicher-Limits setzen

memory: 512M    # 512 MB
memory: 2G      # 2 GB

Speicher-Limits sind hart. Überschreitung – Container wird vom OOM Killer beendet, ohne Diskussion.

Meine Richtwerte:

  • Node.js-Apps: mindestens 512M, in Produktion eher 1G
  • Python-Apps: 256M – 512M
  • PostgreSQL: je nach Verbindungen und Datenvolumen 1G – 4G
  • Redis: 256M – 512M, als Cache ggf. mehr

Praxis-Konfiguration

services:
  app:
    image: myapp:latest
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.25'
          memory: 256M

So nutzt die App maximal 1 CPU und 1 GB Speicher; Docker garantiert mindestens 0,25 CPU und 256 MB.

Hinweis: deploy ist primär für Docker Swarm gedacht. Bei Einzelhost-Deployment mit docker-compose up greifen Ressourcenlimits ab Docker Compose V2 oder mit docker-compose --compatibility. Alternativ die veralteten Parameter mem_limit und cpus – oder direkt deploy (Compose V2.20+).

4. Vollständige Vorlage: produktionsreife YAML zum Kopieren

Jede Säule allein hilft – kombiniert ergibt sich produktionsreife Konfiguration. Beispiel für Web-Service + PostgreSQL + Redis, direkt anpassbar.

Vollständiges Beispiel

version: '3.8'

services:
  # Web-Anwendung
  app:
    image: myapp:latest
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.25'
          memory: 256M
    logging:
      driver: json-file
      options:
        max-size: "10m"   # Max. 10 MB pro Logdatei
        max-file: "3"     # Max. 3 Logdateien

  # PostgreSQL-Datenbank
  db:
    image: postgres:15
    restart: unless-stopped
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: apppassword
      POSTGRES_DB: appdb
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          cpus: '0.5'
          memory: 512M
    volumes:
      - pgdata:/var/lib/postgresql/data

  # Redis-Cache
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          memory: 128M

volumes:
  pgdata:

Konfiguration für Dev und Prod trennen

Entwicklung und Produktion unterscheiden sich meist. Zwei Dateien halten das sauber getrennt:

# compose.yaml – Entwicklung
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    restart: "no"  # In Dev kein Auto-Restart
# compose.production.yaml – Produktions-Override
version: '3.8'
services:
  app:
    image: myapp:v1.2.3  # Fertiges Image in Produktion
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 1G

Produktion starten:

docker-compose -f compose.yaml -f compose.production.yaml up -d

Beide Dateien werden zusammengeführt; compose.production.yaml überschreibt compose.yaml.

Log-Management: Festplatte schützen

Der Standard-Treiber json-file wächst ohne Limit. Ohne Obergrenze ist die Festplatte nach Monaten voll.

logging:
  driver: json-file
  options:
    max-size: "10m"   # Max. 10 MB pro Datei
    max-file: "3"     # Max. 3 Dateien, insgesamt max. 30 MB

Pro Container maximal 30 MB Logs mit automatischer Rotation. Diese Konfiguration setze ich bei jedem Service – kein manuelles Aufräumen mehr nötig.

Fazit

Die Logik der drei Säulen:

Healthcheck erkennt Probleme → Restart-Policy stellt wieder her → Ressourcenlimits verhindern Kettenreaktionen

Damit kann Ihre Docker-Compose-Anwendung:

  1. Beim Start auf bereite Abhängigkeiten warten – statt blind loszulaufen
  2. Nach Absturz selbst wieder hochkommen – ohne dass Sie nachts aufstehen müssen
  3. Einen unkontrollierten Container nicht den ganzen Server mitreißen lassen

Prüfen Sie jetzt Ihre docker-compose.yml. Was fehlt? Ergänzen Sie es.

Wenn Sie Docker Compose noch nicht produktiv nutzen: Nehmen Sie diese drei Konfigurationen beim nächsten Deployment mit. Sie werden es nicht bereuen.

Docker-Compose-Produktion: Drei Säulen konfigurieren

Healthcheck, Restart-Policy und Ressourcenlimits für ein stabiles Produktions-Deployment hinzufügen

⏱️ Estimated time: 15 min

  1. 1

    Step 1: Healthcheck konfigurieren

    Für jeden Service einen healthcheck-Block hinzufügen:

    • test: Prüfbefehl (curl, pg_isready, redis-cli ping usw.)
    • interval: Prüfintervall (empfohlen 10–30s)
    • timeout: Timeout pro Prüfung (empfohlen 5–10s)
    • retries: Anzahl Fehlversuche (empfohlen 3–5)
    • start_period: Start-Toleranzphase (je nach Startzeit 30–60s)
  2. 2

    Step 2: Abhängige Services bedingt starten

    depends_on mit condition-Parameter nutzen:

    • depends_on: [db] ersetzen durch depends_on: db: condition: service_healthy
    • Sicherstellen, dass der abhängige Service einen Healthcheck hat
    • Anwendung startet erst, wenn Abhängigkeiten wirklich bereit sind
  3. 3

    Step 3: Restart-Policy festlegen

    Je nach Service-Typ die passende Policy wählen:

    • Kerndienste (Web/API/Datenbank): restart: unless-stopped
    • Hintergrundjobs/Cron-Skripte: restart: on-failure:5
    • Entwicklung/Debug: restart: "no"
    • always vermeiden (startet nach manuellem Stop unerwartet neu)
  4. 4

    Step 4: Ressourcenlimits setzen

    In deploy.resources limits und reservations konfigurieren:

    • limits: harte Grenze – Überschreitung führt zum OOM Killer
    • reservations: weiche Grenze – von Docker garantiertes Minimum
    • Node.js-Apps: limits.memory mindestens 512M
    • Datenbank je nach Verbindungen und Datenvolumen 1G–4G
  5. 5

    Step 5: Log-Rotation konfigurieren

    Verhindern, dass Logdateien die Festplatte füllen:

    • logging.driver: json-file (Standard-Treiber)
    • logging.options.max-size: "10m" (max. 10 MB pro Datei)
    • logging.options.max-file: "3" (3 Dateien behalten)
    • Gesamt-Obergrenze 30 MB mit automatischer Rotation

FAQ

Startet der Container nach fehlgeschlagenem Healthcheck automatisch neu?
Nein. Ein Healthcheck markiert den Container nur als unhealthy – er löst keinen Neustart aus. Kombinieren Sie Healthcheck mit Restart-Policy (z. B. unless-stopped) und depends_on mit service_healthy. Erst wenn der Container-Prozess abstürzt, greift die Restart-Policy.
Was ist der Unterschied zwischen unless-stopped und always?
Der entscheidende Unterschied liegt im Verhalten nach manuellem Stop: Bei always startet der Container nach Server- oder Docker-Neustart wieder – auch wenn Sie ihn manuell mit docker stop angehalten haben. Bei unless-stopped bleibt er nach manuellem Stop gestoppt. Für Produktion wird unless-stopped empfohlen.
Was ist der Unterschied zwischen limits und reservations?
limits ist eine harte Grenze – überschreitet der Container limits.memory, wird er vom OOM Killer beendet. reservations ist eine weiche Grenze: Sie teilt dem Docker-Scheduler mit, wie viel Ressourcen mindestens reserviert werden sollen; der Container darf darüber hinaus nutzen. CPU-Limits sind weich (Drosselung), Speicherlimits hart (Prozess wird beendet).
Wozu dient der Parameter start_period?
start_period ist eine Start-Toleranzphase. Fehlgeschlagene Healthchecks in dieser Zeit zählen nicht zu retries. Bei langsam startenden Apps (z. B. 40 Sekunden für DB-Verbindung) verhindert start_period: 60s, dass die Anwendung direkt nach dem Start fälschlich als unhealthy markiert wird.
Wie nutzt man unterschiedliche Konfigurationen für Entwicklung und Produktion?
Zwei Konfigurationsdateien trennen:

• compose.yaml: Entwicklung (build: ., restart: "no")
• compose.production.yaml: Produktions-Override (image: xxx, restart: unless-stopped)
• Startbefehl: docker-compose -f compose.yaml -f compose.production.yaml up -d
• Die zweite Datei überschreibt die erste – saubere Trennung der Umgebungen
Kann man deploy.resources auf einem Einzelhost nutzen?
Ja. Obwohl deploy vor allem für Docker Swarm gedacht ist, unterstützt Docker Compose ab V2.20 Einzelhost-Deployments. Bei älteren Versionen --compatibility verwenden oder die veralteten Parameter mem_limit/cpus nutzen. Upgrade auf die neueste Docker-Compose-Version empfohlen.

7 Min. Lesezeit · Veröffentlicht am: 24. Apr. 2026 · Aktualisiert am: 14. Juli 2026

Ähnliche Beiträge

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog