Fehlerbehebung bei Docker-Containern: Häufige Startprobleme und Lösungen

Diagnostizieren Sie Docker-Container, die beendet werden, Ports nicht binden können, Dateien vermissen, auf Berechtigungsprobleme stoßen oder aufgrund von Speichermangel beendet werden.

Fehlerbehebung bei Docker-Containern: Häufige Startprobleme und Lösungen

Wenn ein Docker-Container nicht startet, besteht die schnellste Lösung meist darin, dem Drang zu widerstehen, zu raten. Ein Container ist lediglich ein Prozess mit einem Dateisystem, einer Umgebung, Netzwerkeinstellungen und Limits, die ihn umgeben. Wenn dieser Prozess beendet wird, zeichnet Docker den Grund auf. Ihre Aufgabe ist es, die Beweise in der richtigen Reihenfolge zu sammeln.

Ich beginne normalerweise mit drei Fragen: Hat Docker den Container erstellt, wurde der Hauptprozess gestartet, und hat etwas außerhalb des Prozesses ihn beendet oder blockiert? Diese Fragen trennen einen falschen Image-Namen von einem defekten Befehl, einen Portkonflikt von einem Anwendungsabsturz und ein Berechtigungsproblem von einem Speicherlimit.

Beginnen Sie mit dem langweiligen Befehl, der Ihnen die Wahrheit sagt:

docker ps -a

Schauen Sie sich STATUS, PORTS und NAMES an. Created bedeutet, dass Docker den Container erstellt, aber nicht tatsächlich gestartet hat. Exited (1) bedeutet oft, dass die Anwendung einen normalen Fehler zurückgegeben hat. Exited (127) deutet häufig auf einen fehlenden Befehl hin. Exited (137) bedeutet oft, dass der Prozess von außen beendet wurde, häufig aufgrund von Speicherdruck. Diese Codes sind Hinweise, keine endgültigen Antworten, aber sie verhindern, dass Sie die falsche Schicht debuggen.

Lesen Sie dann die Logs:

docker logs --tail 100 <container>
docker logs -f <container>

Wenn der Container sofort stirbt, ist docker logs normalerweise nützlicher, als denselben docker run-Befehl erneut auszuführen. Anwendungsframeworks geben oft die genaue fehlende Umgebungsvariable, einen Migrationsfehler, eine ungültige Konfigurationsdatei oder einen Bindungsfehler aus, bevor sie beendet werden.

Für den Low-Level-Zustand inspizieren Sie den Container:

docker inspect <container> --format '{{json .State}}'
docker inspect <container> --format 'exit={{.State.ExitCode}} oom={{.State.OOMKilled}} error={{.State.Error}}'

Der zweite Befehl ist es wert, auswendig gelernt zu werden. Er sagt Ihnen, ob Docker einen OOM-Kill gesehen hat, welcher Exit-Code aufgezeichnet wurde und ob die Laufzeitumgebung selbst einen Fehler hatte.

Wenn der Container sofort beendet wird

Ein Container bleibt nur so lange aktiv, wie sein Hauptprozess aktiv bleibt. Wenn der Befehl beendet wird, stoppt Docker den Container. Das überrascht Leute, wenn sie Skripte ausführen, die einen Daemon im Hintergrund starten und dann zurückkehren.

Zum Beispiel wird dieses Muster oft beendet:

CMD service nginx start

Der Service-Befehl kann nginx starten und dann beenden. Docker sieht, dass der Hauptprozess endet, und stoppt den Container. Das containerfreundliche Muster besteht darin, den Server im Vordergrund auszuführen:

CMD ["nginx", "-g", "daemon off;"]

Das gleiche Prinzip gilt für Node-, Python-, Java- und Worker-Prozesse. Der Befehl in CMD oder ENTRYPOINT sollte der langlebige Prozess sein, kein Starter, der die eigentliche Arbeit in den Hintergrund verschiebt und beendet wird.

Wenn Logs command not found, no such file or directory oder exec format error anzeigen, testen Sie das Image interaktiv:

docker run --rm -it --entrypoint sh <image>

Einige Images enthalten kein bash, insbesondere Alpine- und distroless-ähnliche Images. Verwenden Sie zuerst sh, es sei denn, Sie wissen, dass bash existiert. Überprüfen Sie dann den Dateipfad, die Berechtigungen und den Interpreter:

ls -l /app
which python || true
head -1 /app/start.sh

Ein Skript kann existieren und trotzdem mit no such file or directory fehlschlagen, wenn sein Shebang auf einen fehlenden Interpreter verweist, wie #!/bin/bash in einem Image, das nur /bin/sh hat. Eine weitere häufige Ursache sind Windows-Zeilenumbrüche. Wenn ein Shell-Skript unter Windows bearbeitet wurde, kann das unsichtbare \r dazu führen, dass Linux nach /bin/sh\r sucht.

Wenn Docker sagt, der Port sei bereits belegt

Portkonflikte treten auf der Host-Seite auf. In -p 8080:80 ist 8080 der Host-Port und 80 der Container-Port. Wenn bereits etwas auf dem Host-Port 8080 lauscht, kann Docker ihn nicht binden.

Möglicherweise sehen Sie einen Fehler wie bind: address already in use oder port is already allocated. Finden Sie den Lauscher:

sudo lsof -i :8080
# oder
sudo ss -ltnp 'sport = :8080'

Auf macOS ist lsof normalerweise am einfachsten. Auf Linux-Servern ist ss oft standardmäßig verfügbar. Verwenden Sie in Windows PowerShell:

Get-NetTCPConnection -LocalPort 8080

Wählen Sie dann einen anderen Host-Port oder stoppen Sie den Dienst, der ihn besitzt:

docker run -d -p 8081:80 nginx

Ändern Sie den Container-Port nicht, es sei denn, die Anwendung im Container lauscht tatsächlich auf diesem neuen Port. Wenn nginx innerhalb des Containers auf Port 80 lauscht, ist -p 8081:80 korrekt. -p 8081:8081 wird vom Browser aus fehlschlagen, wenn nichts im Container auf Port 8081 lauscht.

Wenn die App startet, aber keine Konfiguration finden kann

Viele Startfehler sind fehlende Umgebungsvariablen. Das Image ist in Ordnung, der Befehl ist in Ordnung, aber die App erwartet DATABASE_URL, REDIS_URL, einen API-Schlüssel oder eine Konfigurationsdatei.

Überprüfen Sie, was Docker übergeben hat:

docker inspect <container> --format '{{range .Config.Env}}{{println .}}{{end}}'

Für Compose-Projekte inspizieren Sie die aufgelöste Konfiguration, anstatt nur docker-compose.yml zu lesen:

docker compose config

Dies fängt Einrückungsfehler, Überraschungen in .env-Dateien und Variablen, die zu leeren Zeichenfolgen expandiert wurden, ab. Ein reales Beispiel: DATABASE_URL=${DATABASE_URL} sieht harmlos aus, aber wenn die Shell oder die .env-Datei sie nicht definiert, kann Ihre Anwendung einen leeren Wert erhalten und beim Start fehlschlagen.

Seien Sie vorsichtig mit Geheimnissen in Logs und Terminal-Historie. Für schnelles lokales Debuggen ist die Übergabe von -e NAME=value in Ordnung. Verwenden Sie für gemeinsam genutzte Systeme den Secret-Mechanismus Ihrer Plattform oder eine Umgebungsdatei mit kontrollierten Berechtigungen.

Wenn Bind-Mounts oder Volumes Berechtigungsfehler verursachen

Ein Container kann beim Start fehlschlagen, weil er eine Konfigurationsdatei nicht lesen, eine PID-Datei schreiben, ein Cache-Verzeichnis erstellen oder ein Datenbankverzeichnis initialisieren kann. Die Logs sagen normalerweise permission denied, read-only file system oder operation not permitted.

Inspizieren Sie zuerst den Mount:

docker inspect <container> --format '{{json .Mounts}}'

Überprüfen Sie dann, als welcher Benutzer der Container läuft:

docker inspect <container> --format 'user={{.Config.User}}'

Wenn user leer ist, läuft das Image möglicherweise standardmäßig als root, aber viele Produktionsimages setzen einen Nicht-Root-Benutzer. Ein Host-Verzeichnis, das Ihrem lokalen UID gehört, ist möglicherweise nicht für UID 1000, 1001 oder einen dienstspezifischen Benutzer im Container beschreibbar.

Eine praktische Debug-Sequenz ist:

ls -ld ./data
docker run --rm -it -v "$PWD/data:/data" --entrypoint sh <image>
id
ls -ld /data
touch /data/test

Vermeiden Sie es, jedes Berechtigungsproblem mit chmod 777 zu lösen. Es kann das unmittelbare Problem verbergen, während ein schlimmeres entsteht. Bevorzugen Sie die Anpassung der Eigentümerschaft oder die Verwendung benannter Volumes für Anwendungsdaten:

docker volume create app_data
docker run -d -v app_data:/var/lib/app <image>

Benannte Volumes sind besonders nützlich auf Docker Desktop, wo Bind-Mounts eine Virtualisierungsgrenze überschreiten und sich anders verhalten können als native Linux-Dateisysteme.

Wenn der Container wegen Speicher beendet wurde

Exit-Code 137 ist ein starker Hinweis darauf, dass der Prozess SIGKILL erhalten hat. In der Docker-Arbeit bedeutet das oft, dass der Kernel oder Docker Desktop ihn beendet hat, weil der Speicher ausgegangen ist. Bestätigen Sie mit inspect:

docker inspect <container> --format 'exit={{.State.ExitCode}} oom={{.State.OOMKilled}}'

Wenn OOMKilled true ist, haben Sie zwei Aufgaben: Geben Sie dem Prozess genügend Speicher zum Starten und verstehen Sie, warum er so viel benötigt hat. Das Erhöhen des Limits kann die richtige Produktionslösung für einen Datenbank- oder JVM-Dienst sein. Bei einem kleinen Webdienst kann es eine schlechte Standardeinstellung offenbaren.

Java-Apps sind ein klassisches Beispiel. Älteres JVM-Verhalten passte nicht immer gut zu Container-Limits, und selbst moderne JVMs benötigen noch sinnvolle -Xmx- oder prozentbasierte Einstellungen für vorhersagbares Verhalten. Node-Dienste benötigen möglicherweise --max-old-space-size in speicherbeschränkten Umgebungen. Datenbanken benötigen möglicherweise explizite Cache-Einstellungen.

Für einen einmaligen Test:

docker run --memory=1g <image>

Wenn Sie Docker Desktop verwenden, überprüfen Sie auch den der Docker-VM zugewiesenen Speicher. Ein Container-Limit kann nicht helfen, wenn die VM selbst ausgehungert ist.

Wenn das Image nie gepullt wird oder der Build nie ein Image erzeugt hat

Manchmal gibt es kein Container-Problem, weil es kein verwendbares Image gibt. Wenn docker run vor dem Erstellen eines Containers fehlschlägt, überprüfen Sie das Image separat:

docker image ls | grep my-app
docker pull my-registry/my-app:tag

Für private Registrierungen bestätigen Sie die Authentifizierung:

docker login <registry>

Für lokale Images stellen Sie sicher, dass der Tag, den Sie ausführen, der Tag ist, den Sie gebaut haben:

docker build -t my-app:dev .
docker run --rm my-app:dev

Ein häufiger lokaler Fehler ist das Bauen von my-app:dev und das Ausführen von my-app:latest, was auf ein älteres Image oder gar nichts verweisen kann.

Wenn Netzwerkprobleme vermutet werden, der Dienst aber nicht lauscht

Wenn ein Browser einen Container nicht erreichen kann, springen Leute oft zu Docker-Netzwerk. Beweisen Sie zuerst, dass die Anwendung innerhalb des Containers lauscht.

docker exec -it <container> sh
ss -ltnp || netstat -ltnp

Wenn die App innerhalb des Containers an 127.0.0.1 gebunden ist, hilft Docker-Port-Publishing nicht. Die App muss auf 0.0.0.0 oder der Schnittstellenadresse des Containers lauschen. Dies ist bei Entwicklungsservern üblich. Viele Frameworks standardisieren auf localhost und benötigen ein Flag wie --host 0.0.0.0.

Bestätigen Sie dann den veröffentlichten Port:

docker port <container>
docker ps --format 'table {{.Names}}\t{{.Ports}}'

Sie möchten etwas wie 0.0.0.0:8080->3000/tcp sehen. Wenn kein veröffentlichter Port vorhanden ist, funktioniert der Dienst möglicherweise von einem anderen Container im selben Netzwerk, aber nicht von Ihrem Host-Browser.

Eine zuverlässige Start-Checkliste

Verwenden Sie diese Reihenfolge, wenn Sie feststecken:

  1. docker ps -a, um zu sehen, ob der Container existiert und wie er beendet wurde.
  2. docker logs --tail 100 <container>, um die eigene Beschwerde der Anwendung zu lesen.
  3. docker inspect <container>, um Exit-Code, OOM-Status, Befehl, Benutzer, Mounts und Ports zu überprüfen.
  4. docker run --rm -it --entrypoint sh <image>, um das Image von Hand zu testen.
  5. Entfernen Sie jeweils eine Variable: zuerst ohne Mounts, dann ohne benutzerdefinierte Netzwerke, dann nur mit den erforderlichen Umgebungsvariablen.

Dieser letzte Schritt ist wichtig. Ein langer docker run-Befehl mit Ports, Volumes, Env-Dateien, benutzerdefiniertem DNS, Speicherlimits und einem benutzerdefinierten Entrypoint gibt Ihnen zu viele Verdächtige. Reduzieren Sie ihn, bis das Image startet, und fügen Sie dann Einstellungen hinzu, bis es bricht. Die Einstellung, die Sie gerade hinzugefügt haben, ist normalerweise der Ort, an dem das eigentliche Problem liegt.