Container-Restart-Loops erkennen und systematisch debuggen
AI generated
FROM
RUN
Docker · Debugging · Betrieb
Container-Restart-Loops systematisch debuggen
Wenn ein Container sich selbst immer wieder tötet

Ein Container, der in einer Endlosschleife startet, kurz läuft und dann wieder abstürzt, ist eines der frustrierendsten Alltagsprobleme im Docker-Betrieb, weil die Logs oft schneller verschwinden, als man sie lesen kann. Mit einem systematischen Vorgehen lässt sich die Ursache meist innerhalb weniger Minuten eingrenzen, statt wild an Konfigurationswerten herumzuraten.

17 Min. Lesezeit Restart Policy Logs vor dem Crash Healthcheck-Fallstricke

1. Was eine Restart-Loop technisch bedeutet

Eine Restart-Loop entsteht, wenn ein Container mit einer Restart Policy wie always, on-failure oder unless-stopped läuft, der Hauptprozess aber unmittelbar nach dem Start mit einem Fehler beendet wird. Docker interpretiert diese Restart Policy strikt und startet den Container gemäß der Konfiguration erneut, was ihn erneut mit demselben Fehler abstürzen lässt, wodurch der Zyklus sich unbegrenzt wiederholt.

Der Container-Status zeigt in diesem Zustand meist einen ständigen Wechsel zwischen Restarting und kurzzeitig Up, was sich mit docker ps gut beobachten lässt. Anders als bei Kubernetes gibt es in reinem Docker keinen expliziten CrashLoopBackOff-Status mit exponentiellem Backoff, Docker erhöht aber ab Docker Engine intern zunehmend die Wartezeit zwischen Neustartversuchen, um die Systemlast zu begrenzen.


# Restart-Loop anhand des Status erkennen
docker ps -a --filter "name=webshop_app"
# STATUS: Restarting (1) 3 seconds ago

# Anzahl der bisherigen Neustarts anzeigen
docker inspect webshop_app --format '{{.RestartCount}}'

2. Schritt 1: Logs vor dem letzten Crash sichern

Der wichtigste erste Schritt ist, die Logausgabe des Containers zu sichern, bevor ein weiterer Neustart die relevanten Fehlermeldungen im Terminal verdrängt. Der Standardbefehl docker logs zeigt zunächst nur die aktuelle Laufzeit, während die Option --since in Kombination mit einer Umleitung in eine Datei erlaubt, den relevanten Ausschnitt dauerhaft zu sichern, statt ihn im scrollenden Terminal zu verlieren.

Besonders wertvoll ist die Option docker logs --previous-ähnliches Verhalten über den Container-Namen selbst, da Docker die Logs der vorherigen Ausführung eines neu gestarteten Containers weiterhin unter derselben Container-ID vorhält, solange der Container nicht vollständig entfernt wurde. Ein einfacher Redirect in eine Datei verhindert zudem, dass die Ausgabe bei einem sehr schnellen Crash-Zyklus im Terminal-Buffer verloren geht.


# Alle bisherigen Logs mit Zeitstempeln sichern
docker logs -t webshop_app > /tmp/webshop_app_crash.log 2>&1

# Nur die letzten 200 Zeilen vor dem aktuellen Neustart
docker logs -t --tail 200 webshop_app

# Live mitschneiden waehrend des naechsten Crashs
docker logs -f webshop_app | tee /tmp/webshop_live.log

3. Schritt 2: Restart Policy temporär deaktivieren

Solange die Restart Policy aktiv bleibt, startet Docker den Container nach jedem Absturz automatisch neu, was das gezielte Debugging erschwert, weil der Container sich unter Umständen bereits wieder neu startet, während man noch den vorherigen Fehler analysiert. Der zuverlässigste nächste Schritt ist deshalb, die Restart Policy vorübergehend auf no zu setzen, damit der Container nach einem Absturz im gestoppten Zustand verharrt.

Mit einem gestoppten Container lässt sich anschließend gezielt der letzte Exit-Code auslesen, der oft bereits einen entscheidenden Hinweis liefert: Exit-Code 1 deutet meist auf einen allgemeinen Anwendungsfehler hin, Exit-Code 137 auf ein durch den Kernel erzwungenes SIGKILL, häufig ausgelöst durch ein Out-of-Memory-Ereignis, und Exit-Code 143 auf ein reguläres SIGTERM, das die Anwendung nicht innerhalb der Grace-Period sauber verarbeitet hat.


# Restart Policy vorübergehend deaktivieren
docker update --restart=no webshop_app

# Exit-Code der letzten Ausfuehrung pruefen
docker inspect webshop_app --format '{{.State.ExitCode}}'

# Nach der Analyse: Policy wiederherstellen
docker update --restart=unless-stopped webshop_app

4. Häufige Ursache 1: Fehlende oder falsche Umgebungsvariable

Eine der häufigsten Ursachen für Restart-Loops ist eine fehlende, leere oder falsch benannte Umgebungsvariable, auf die die Anwendung beim Start zwingend angewiesen ist, etwa eine Datenbank-Zugangsdaten-Variable oder ein API-Secret. Viele Anwendungsframeworks brechen den Start bewusst mit einem klaren Fehler ab, wenn eine Pflichtvariable fehlt, was sich als schnelle, wiederholte Absturzsequenz zeigt, sobald die Fehlermeldung nur im überfluteten Log untergeht.

Ein gezielter Vergleich der tatsächlich im Container gesetzten Umgebungsvariablen mit der Erwartungshaltung der Anwendung, etwa über eine .env.example-Datei, deckt solche Probleme meist innerhalb weniger Sekunden auf. Besonders tückisch sind dabei stille Tippfehler im Variablennamen, die von der Anwendung nicht als fehlend erkannt werden, sondern schlicht ignoriert werden und zu einem Fallback-Wert führen, der erst später zu einem Fehler führt.


# Tatsaechlich gesetzte Umgebungsvariablen im Container pruefen
docker exec webshop_app env | sort

# Mit der erwarteten Konfiguration vergleichen
docker exec webshop_app env | sort > /tmp/actual.env
sort .env.example > /tmp/expected.env
diff /tmp/expected.env /tmp/actual.env

5. Häufige Ursache 2: Datenbank noch nicht bereit

Ein weiteres klassisches Muster tritt bei Multi-Container-Setups auf, in denen eine Anwendung beim Start sofort eine Verbindung zu einer Datenbank herstellen möchte, die selbst noch initialisiert. Der Datenbank-Container meldet zwar bereits den Status Up, akzeptiert aber noch keine Verbindungen, weil interne Initialisierungsschritte wie das Anlegen von Systemtabellen noch laufen, was insbesondere bei MySQL und PostgreSQL beim allerersten Start mehrere Sekunden dauern kann.

Die Anwendung erhält daraufhin einen Verbindungsfehler und beendet sich, woraufhin die Restart Policy sie neu startet, oft schneller als die Datenbank ihre Initialisierung abschließt, was zu mehreren gescheiterten Versuchen in Folge führt. Die zuverlässige Lösung ist ein Healthcheck auf dem Datenbank-Container in Kombination mit depends_on und der Bedingung service_healthy in docker-compose, statt sich auf die reine Prozess-Laufzeit zu verlassen.


# docker-compose.yml: korrekte Abhaengigkeit auf Healthcheck
services:
  db:
    image: mysql:8.0
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 3s
      retries: 10
  app:
    depends_on:
      db:
        condition: service_healthy

6. Häufige Ursache 3: Ein zu aggressiver Healthcheck

Paradoxerweise kann auch ein zu streng konfigurierter Healthcheck selbst eine Restart-Loop auslösen, wenn er in Kombination mit einem externen Orchestrator oder einem Restart-Skript so eingerichtet ist, dass ein Container bei einem fehlgeschlagenen Health-Status aktiv neu gestartet wird. Ein zu kurzes interval in Kombination mit einer Anwendung, die etwas länger als erwartet für den Start benötigt, etwa wegen eines Cache-Warmup, führt dazu, dass der Container als ungesund markiert wird, obwohl er sich noch in einer regulären Startphase befindet.

Die Option start_period im Healthcheck ist dafür genau das richtige Werkzeug: Sie definiert eine Anlaufzeit, in der fehlgeschlagene Healthchecks noch nicht als Fehler gezählt werden, sondern der Anwendung Zeit für ihre eigentliche Initialisierung gegeben wird. Wird diese Option vergessen oder zu knapp bemessen, wirkt ein an sich funktionierender Container fälschlicherweise dauerhaft ungesund und wird von übergeordneten Systemen unnötig neu gestartet.


# Healthcheck mit ausreichender Anlaufzeit
docker run -d \
  --health-cmd="curl -f http://localhost/health || exit 1" \
  --health-interval=10s \
  --health-timeout=3s \
  --health-retries=3 \
  --health-start-period=45s \
  webshop_app

7. Speicherprobleme: Wenn der Kernel den Container selbst tötet

Wenn ein Container mit einem festen Speicherlimit gestartet wurde und die Anwendung dieses Limit überschreitet, greift der Out-of-Memory-Killer des Linux-Kernels ein und beendet den Hauptprozess zwangsweise mit SIGKILL. Dieser Fall zeigt sich zuverlässig am Exit-Code 137 sowie an der Kernel-Warnung, die sich außerhalb des Containers im Host-System-Log findet, während der Container selbst dabei oft keine aussagekräftige Fehlermeldung mehr ausgeben kann, da der Prozess ohne Vorwarnung beendet wird.

Zur Bestätigung dieser Ursache lohnt sich ein Blick auf das Feld OOMKilled in der Docker-Inspect-Ausgabe, das explizit anzeigt, ob der letzte Absturz durch den Out-of-Memory-Killer ausgelöst wurde. Die Lösung liegt entweder in einer Erhöhung des Speicherlimits oder, häufiger sinnvoll, in der Analyse und Behebung eines tatsächlichen Speicherlecks in der Anwendung selbst.


# Pruefen, ob der letzte Absturz durch OOM verursacht wurde
docker inspect webshop_app --format '{{.State.OOMKilled}}'

# Aktuelles Speicherlimit und Nutzung vergleichen
docker inspect webshop_app --format '{{.HostConfig.Memory}}'
docker stats webshop_app --no-stream

8. Vorbeugen statt debuggen: Restart-Loops von vornherein vermeiden

Ein Großteil der hier beschriebenen Ursachen lässt sich bereits vor dem ersten Absturz entschärfen, indem Anwendungen so geschrieben werden, dass sie beim Fehlen einer Pflichtkonfiguration eine klare, sofort erkennbare Fehlermeldung ausgeben, statt kryptisch abzubrechen. Ebenso hilfreich ist ein eingebauter Retry-Mechanismus mit begrenzter Anzahl an Versuchen für externe Abhängigkeiten wie Datenbankverbindungen, statt beim ersten Verbindungsfehler sofort den gesamten Prozess zu beenden.

In Compose-Setups lohnt sich außerdem der bewusste Verzicht auf eine zu aggressive Restart Policy während der aktiven Entwicklungsphase, etwa durch die vorübergehende Nutzung von on-failure:3 statt always, damit ein fehlerhafter Container nach wenigen Versuchen stehen bleibt und Zeit für eine Analyse lässt, statt unbegrenzt weiterzulaufen und die Logs mit identischen Fehlermeldungen zu überfluten.


# Begrenzte Restart Policy waehrend der Entwicklung
docker run -d --restart=on-failure:3 webshop_app

# In docker-compose.yml aequivalent
# restart: on-failure:3

9. Der systematische Debugging-Ablauf im Überblick

Statt bei jeder Restart-Loop die Ursachen in zufälliger Reihenfolge zu prüfen, empfiehlt sich ein fester Ablauf: zuerst die Logs sichern, dann die Restart Policy deaktivieren, dann den Exit-Code auslesen, dann gezielt anhand des Exit-Codes zwischen Umgebungsvariablen, Abhängigkeiten, Healthcheck-Konfiguration und Speicherlimits unterscheiden. Dieser Ablauf lässt sich vollständig innerhalb weniger Minuten durchlaufen und deckt die überwiegende Mehrheit der in der Praxis auftretenden Fälle ab.

Die folgende Tabelle ordnet die häufigsten Exit-Codes und Symptome den jeweils wahrscheinlichsten Ursachen zu, um die Fehlersuche von der ersten Beobachtung an gezielt zu lenken, statt bei jedem neuen Vorfall wieder bei null zu beginnen.

Symptom / Exit-Code Wahrscheinlichste Ursache Erster Prüfschritt Typische Lösung
Exit-Code 1 Allgemeiner Anwendungsfehler, z. B. fehlende Umgebungsvariable docker logs -t --tail 200 Konfiguration bzw. Umgebungsvariablen korrigieren
Exit-Code 137 Out-of-Memory-Kill durch den Kernel docker inspect --format '{{.State.OOMKilled}}' Speicherlimit erhöhen oder Speicherleck beheben
Exit-Code 143 Reguläres SIGTERM, kein sauberes Shutdown Shutdown-Handling der Anwendung prüfen Graceful-Shutdown-Logik implementieren
Sofortiger Absturz nach Datenbankverbindung Datenbank noch nicht bereit docker logs des Datenbank-Containers Healthcheck plus depends_on: service_healthy
Container als unhealthy trotz laufender App Zu aggressiver oder zu früher Healthcheck docker inspect --format '{{.State.Health}}' start_period im Healthcheck erhöhen

Mironsoft

Container-Infrastruktur, CI-Pipelines und Deployment-Automatisierung

Docker-Setups, die im Team und in Produktion tragfähig bleiben?

Wir prüfen bestehende Dockerfiles und Compose-Stacks auf Sicherheitslücken, aufgeblähte Images und fragile Build-Pipelines und bauen daraus eine Container-Infrastruktur, die schnell baut, sicher läuft und im Team nachvollziehbar bleibt.

Dockerfile-Review

Multi-Stage-Builds, Layer-Caching und Image-Größe systematisch optimieren.

Security-Audit

Container-Isolation, Secrets-Handling und Image-Scanning gegen echte Angriffsflächen absichern.

CI/CD-Integration

Build-Pipelines, Registries und Deployment-Strategien für reproduzierbare Releases aufbauen.

10. Zusammenfassung

Restart-Loops debuggen: Das Wichtigste auf einen Blick

Erster Schritt

Logs immer sofort sichern, bevor der nächste automatische Neustart sie überschreibt.

Restart Policy

Temporär auf 'no' setzen, um den Container im gestoppten Zustand analysieren zu können.

Exit-Codes

137 deutet auf OOM-Kill, 1 meist auf Anwendungsfehler wie fehlende Umgebungsvariablen.

Häufigste Ursache

Fehlende Umgebungsvariablen und zu früh gestartete Anwendungen vor bereiten Abhängigkeiten.

11. FAQ: Restart-Loops debuggen: Das Wichtigste auf einen Blick

1Was ist eine Restart-Loop bei Docker-Containern?
Ein Zustand, in dem ein Container mit aktiver Restart Policy unmittelbar nach dem Start abstürzt, woraufhin Docker ihn automatisch neu startet, was denselben Fehler erneut auslöst und den Zyklus unbegrenzt wiederholt.
2Wie erkenne ich eine Restart-Loop mit docker ps?
Der Status-Spalte zeigt wiederholt 'Restarting' gefolgt von kurzzeitigem 'Up'. Die Anzahl der bisherigen Neustarts lässt sich zusätzlich mit docker inspect --format '{{.RestartCount}}' auslesen.
3Warum sollte ich zuerst die Logs sichern, bevor ich weiter debugge?
Weil ein weiterer automatischer Neustart die im Terminal sichtbaren Fehlermeldungen der vorherigen Ausführung verdrängen kann. Ein Redirect in eine Datei mit docker logs -t sichert den relevanten Ausschnitt dauerhaft.
4Wie deaktiviere ich die Restart Policy vorübergehend?
Mit docker update --restart=no . Der Container bleibt nach dem nächsten Absturz im gestoppten Zustand, was eine gezielte Analyse des letzten Exit-Codes und der Logs ermöglicht.
5Was bedeutet Exit-Code 137 bei einem abgestürzten Container?
Exit-Code 137 deutet fast immer auf ein durch den Kernel erzwungenes SIGKILL hin, meist ausgelöst durch den Out-of-Memory-Killer, wenn der Container sein konfiguriertes Speicherlimit überschritten hat.
6Wie finde ich heraus, ob ein Absturz durch Speichermangel verursacht wurde?
Mit docker inspect --format '{{.State.OOMKilled}}'. Der Rückgabewert true bestätigt, dass der Out-of-Memory-Killer des Kernels den Container beendet hat.
7Warum stürzt meine Anwendung ab, obwohl die Datenbank laut docker ps läuft?
Der Status Up bedeutet nur, dass der Datenbank-Prozess gestartet ist, nicht dass er bereits Verbindungen akzeptiert. Interne Initialisierungsschritte können danach noch mehrere Sekunden dauern.
8Wie verhindere ich, dass eine Anwendung vor einer bereiten Datenbank startet?
Mit einem Healthcheck auf dem Datenbank-Container in Kombination mit depends_on und der Bedingung service_healthy in docker-compose, statt sich nur auf die reine Prozess-Laufzeit zu verlassen.
9Kann ein Healthcheck selbst eine Restart-Loop auslösen?
Ja, wenn interval und start_period zu knapp bemessen sind und die Anwendung länger für ihre Initialisierung braucht, wird sie fälschlich als unhealthy markiert und von übergeordneten Systemen unnötig neu gestartet.
10Was ist die Aufgabe der Option start_period im Healthcheck?
Sie definiert eine Anlaufzeit, in der fehlgeschlagene Healthchecks noch nicht als Fehler gewertet werden, sodass die Anwendung ausreichend Zeit für ihre reguläre Startphase bekommt.