Ein großes Docker Engine-Upgrade auf einem Live-Produktionsserver
Ein Upgrade von Docker Engine auf einem Server, der aktiv mehr als hundert Container betreibt, ist keine Aufgabe, die Improvisation belohnt. Als es Zeit war, von Docker Engine 28 auf 29 und Docker Compose v2 auf v5 auf unserem Hetzner-Produktionsserver zu migrieren, gingen wir es mit derselben Disziplin an, die wir bei einer Datenbankmigration anwenden würden: eine schriftliche Vorbereitungscheckliste, ein getesteter Rollback-Pfad, eine präzise Ausführungssequenz und eine Post-Upgrade-Verifizierungsphase, bevor wir das Wartungsfenster schlossen.
Im Folgenden finden Sie einen vollständigen Bericht darüber, wie wir es angegangen sind – die Begründung hinter jedem Schritt, die Befehle, die wir tatsächlich ausgeführt haben, und worauf man achten sollte, wenn die Containeranzahl hoch genug ist, dass eine falsch konfigurierte Restart-Policy eine schwierige Wiederherstellung auslösen kann.
Warum dieses Upgrade ein formales Runbook verdiente
Docker Engine 29 führte Änderungen am containerd-Integrationspfad ein und passte an, wie docker compose die Abhängigkeitsauflösung in depends_on-Blöcken mit condition: service_healthy handhabt. Docker Compose v5 ist derweil ein erhebliches internes Umschreiben – der Wechsel vom Go-basierten v2-Binär zu einer neuen Architektur, die mehrere langjährige Probleme mit der parallelen Startreihenfolge behebt, aber auch eine Handvoll Compose-Datei-Direktiven veraltert, die v2 stillschweigend ignorierte.
Auf einem Server, wo eine einzelne docker-compose.yml zwölf voneinander abhängige Dienste steuern kann – Datenbanken, Reverse Proxies, Anwendungscontainer, Background-Worker – ist eine stille Verhaltensänderung bei der Abhängigkeitsauflösung genau die Art von Problem, das sich nicht sofort zeigt. Es zeigt sich um 03:00 Uhr, wenn ein Healthcheck nie ausgelöst wird und ein abhängiger Container endlos neu startet.
Die praktische Antwort ist Vorbereitung, nicht Vorsicht um ihrer selbst willen.
Vorbereitungscheckliste
Bevor wir ein einziges Paket anfassten, arbeiteten wir folgende Checkliste durch. Jeder Punkt hat einen Grund.
1. Alle laufenden Container und ihre Restart-Policies inventarisieren.
docker ps --format "table {{.Names}} {{.Status}} {{.Image}}" | sort
docker inspect $(docker ps -q) --format '{{.Name}} restart={{.HostConfig.RestartPolicy.Name}}' | sort
Das liefert eine Ausgangsbasis. Container mit restart: always werden versuchen, nach dem Neustart des Docker-Daemons automatisch wieder hochzufahren – was gewünscht ist, aber nur wenn die zugrunde liegenden Compose-Dateien und Images in einem bekannt guten Zustand sind. Jeder Container, der sich zum Zeitpunkt des Upgrades in einer Crash-Loop befindet, wird sich danach immer noch in einer Crash-Loop befinden, und es ist besser, das jetzt zu wissen.
2. Alle Compose-Dateien gegen das v5-Schema validieren, bevor das Upgrade durchgeführt wird.
docker compose config --quiet 2>&1 | grep -i warning
Führen Sie dies in jedem Verzeichnis aus, das eine docker-compose.yml enthält. Compose v5 ist strenger bei veralteten Schlüsseln – insbesondere version: am Anfang von Compose-Dateien (jetzt ignoriert, erzeugt aber eine Warnung) und bei der Verwendung der veralteten links:-Direktive. Warnungen in v5 können in nachfolgenden Releases zu Fehlern werden; es lohnt sich, sie jetzt zu beheben.
3. Verfügbaren Festplattenplatz bestätigen.
df -h /var/lib/docker
docker system df
Das Upgrade wird einen neuen containerd-Shim abrufen und das Docker Engine-Binär ersetzen. Das alte Binär und seine Abhängigkeiten werden nicht immer automatisch bereinigt. Auf einem Server, der ein Jahr lang läuft, zeigt docker system df häufig mehrere Gigabyte wiederverwertbarer Image-Layer. Bereinigen Sie diese vor dem Upgrade, nicht während.
docker system prune -f --volumes
Verwenden Sie --volumes nur, wenn Sie sicher sind, dass keine anonymen Volumes Daten enthalten, die Sie benötigen. Auf unserem Server sind alle persistenten Daten in benannten, explizit deklarierten Volumes gemountet – daher war dies sicher.
4. Die aktuelle Docker Engine-Version und Compose-Version in eine Referenzdatei exportieren.
docker version > /root/docker-pre-upgrade.txt
docker compose version >> /root/docker-pre-upgrade.txt
docker ps -a >> /root/docker-pre-upgrade.txt
Das dauert zehn Sekunden und liefert eine eindeutige Rollback-Referenz, falls etwas schiefgeht und Sie identifizieren müssen, welche Container zum Upgrade-Zeitpunkt liefen.
5. Nachgelagerte Systeme benachrichtigen.
Unser Server betreibt einen selbstgehosteten Supabase-Stack und mehrere n8n-Workflow-Automatisierungsinstanzen. Jeder Webhook, der während eines Container-Neustarts ausgelöst wird, schlägt auf der Senderseite still fehl. Wir setzten ein Wartungsfenster in unserem Uptime-Monitoring-Tool und deaktivierten eingehende Webhooks in n8n für die Dauer.
Der Rollback-Plan
Ein Rollback-Plan ist nur nützlich, wenn man im Voraus entschieden hat, welche Bedingung ihn auslöst. Unserer war einfach: Wenn ein Container, der vor dem Upgrade lief, zehn Minuten nach Abschluss des Upgrades nicht läuft und nicht mit einem docker compose up -d wiederhergestellt werden kann, setzen wir das Docker Engine-Binär zurück.
Das Zurücksetzen von Docker Engine auf einem Debian-basierten System bedeutet, die vorherige Paketversion zu fixieren. Wir notierten den genauen Versions-String, bevor wir begannen:
apt-cache policy docker-ce docker-ce-cli containerd.io docker-compose-plugin
Ausgabe auf unserem Server vor dem Upgrade:
docker-ce:
Installed: 5:28.1.1-1~debian.12~bookworm
Candidate: 5:29.0.1-1~debian.12~bookworm
docker-compose-plugin:
Installed: 2.35.1-1~debian.12~bookworm
Candidate: 2.36.0-1~debian.12~bookworm
Der Rollback-Befehl, falls benötigt:
apt-get install -y docker-ce=5:28.1.1-1~debian.12~bookworm docker-ce-cli=5:28.1.1-1~debian.12~bookworm containerd.io docker-compose-plugin=2.35.1-1~debian.12~bookworm
Wir hielten diesen Befehl in einer Textdatei auf dem Server unter /root/docker-rollback.sh mit Ausführungsrechten, bereit zur Ausführung ohne Tippen unter Druck.
Der Upgrade-Prozess
Das eigentliche Upgrade ist unkompliziert, sobald die Vorbereitung abgeschlossen ist. Die Reihenfolge ist wichtig: zuerst den Paket-Index aktualisieren, dann überprüfen, was installiert werden soll, dann installieren.
Schritt 1: Den apt-Index nur für das Docker-Repository aktualisieren.
apt-get update -o Dir::Etc::sourcelist="sources.list.d/docker.list" -o Dir::Etc::sourceparts="-" -o APT::Get::List-Cleanup="0"
Dadurch wird die Aktualisierung auf das Docker-Repository beschränkt, anstatt alle Quellen zu aktualisieren. Auf einem Produktionsserver ist eine unbeabsichtigte Paketaktualisierung aus einer nicht zusammenhängenden Quelle während eines Docker-Wartungsfensters eine Variable, die Sie nicht brauchen.
Schritt 2: Die Kandidatenversionen bestätigen.
apt-cache policy docker-ce docker-ce-cli containerd.io docker-compose-plugin
Überprüfen Sie, ob der Kandidat dem entspricht, was Sie erwarten, bevor Sie fortfahren. Wenn der Kandidat eine neuere Version zeigt als das, was Sie in der Staging-Umgebung getestet haben, halten Sie inne und evaluieren Sie.
Schritt 3: Installieren.
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
Dadurch wird der Docker-Daemon gestoppt, die Binärdateien ersetzt und der Daemon neu gestartet. Container mit restart: always werden beim Daemon-Neustart wieder hochgefahren. Das Fenster, in dem Container nicht laufen, beträgt auf einem reasonably großen Server typischerweise unter dreißig Sekunden – obwohl dies variiert, je nachdem wie lange containerd zur Initialisierung benötigt.
Den Neustart in einem zweiten Terminal überwachen:
watch -n 2 'docker ps --format "table {{.Names}} {{.Status}}" | sort'
Schritt 4: Die installierten Versionen überprüfen.
docker version
docker compose version
Auf unserem Server bestätigte die Ausgabe:
Docker Engine: 29.0.1
Docker Compose: v2.36.0
Beachten Sie, dass Docker Compose v5 als Version 2.36.x des docker-compose-plugin-Pakets verteilt wird – die „v5"-Bezeichnung bezieht sich auf die interne Umschreib-Version, nicht auf das Paket-Semver. Das verursacht einige Verwirrung in der Dokumentation; die Paketversion ist es, was der apt-Index verfolgt.
Post-Upgrade-Verifizierung
Zehn Minuten nach dem Upgrade führten wir einen strukturierten Verifizierungsdurchlauf durch. Das ist nicht optional – der Daemon-Neustart bringt Container hoch, garantiert aber nicht, dass Container, die von Healthchecks abhängen, diese tatsächlich bestanden haben.
Überprüfen, dass alle Container im erwarteten Zustand sind.
docker ps -a --format "table {{.Names}} {{.Status}} {{.RunningFor}}" | sort
Jeder Container, der Restarting oder Exited zeigt, erfordert sofortige Untersuchung. Auf unserem Server zeigte ein Container – ein Supabase-Analytics-Dienst – Restarting (1). Er befand sich auch vor dem Upgrade in einer niederfrequenten Crash-Loop; das Upgrade verursachte es nicht, aber es zeigte sich nach dem Post-Upgrade-Scan deutlicher. Genau diese Art von vorhandenem Problem existiert das Pre-Upgrade-Inventar, um es zu erfassen.
Healthchecks als bestanden überprüfen.
docker inspect $(docker ps -q) --format '{{.Name}} health={{.State.Health.Status}}' 2>/dev/null | grep -v "health=<no value>" | sort
Container ohne explizite Healthchecks zeigen <no value> – diese herausfiltern. Die wichtigen sind die, von denen Ihre anderen Container über condition: service_healthy abhängen.
Einen Compose-Config-Validierungsdurchlauf auf allen Stacks ausführen.
for dir in /opt/*/; do
if [ -f "${dir}docker-compose.yml" ]; then
echo "--- $dir ---"
docker compose -f "${dir}docker-compose.yml" config --quiet 2>&1
fi
done
Das erkennt alle Compose-Datei-Direktiven, die Compose v5 nun als Warnungen oder Fehler behandelt. Auf unserem Server gaben drei Stacks die version-Schlüssel-Veraltungswarnung aus. Diese sind kosmetisch, aber es lohnt sich, sie im nächsten Wartungszyklus zu beheben.
Die Docker-Daemon-Logs auf Fehler überprüfen.
journalctl -u docker --since "1 hour ago" | grep -i -E "error|warn|fatal"
Ein sauberes Upgrade produziert keine Fehler im Daemon-Log. Warnungen über veraltete Konfigurationen sind erwähnenswert, erfordern aber keine sofortige Maßnahme.
Netzwerkkonnektivität zwischen Containern bestätigen.
docker network ls
docker network inspect bridge --format '{{range .Containers}}{{.Name}} {{end}}'
Docker Engine 29 änderte nicht das Standard-Netzwerktreiber-Verhalten, aber es lohnt sich zu bestätigen, dass benutzerdefinierte Bridge-Netzwerke nach dem Daemon-Neustart immer noch die erwarteten Container-Mitgliedschaften haben.
Was wir beobachteten
Das Upgrade schloss sich in unter zwei Minuten ab. Alle Container, die vor dem Upgrade liefen, liefen danach. Die Docker Compose v5-Änderung, die wir am unmittelbarsten bemerkten, war ein saubereres --dry-run-Ausgabeformat – die neue Version produziert strukturierte, farbige Ausgaben, die es einfacher machen, zu überprüfen, was ein docker compose up tatsächlich tun wird, bevor man es ausführt.
Die depends_on-Healthcheck-Reihenfolge verhielt sich auf unseren Stacks identisch zu v2. Das war zu erwarten – wir hatten die Compose-Dateien im Voraus validiert – aber es war beruhigend zu bestätigen.
Eine wesentliche Änderung: Compose v5 ignoriert das container_name-Feld in Kombination mit scale nicht mehr stillschweigend. Wir hatten einen Stack, der beides verwendete – ein alter Überrest aus der Zeit, bevor wir zu richtigen Replicas wechselten. Compose v5 gab einen klaren Fehler bei docker compose up für diesen Stack aus, wo v2 den Konflikt stillschweigend ignoriert hatte. Die Lösung war eine einzeilige Bearbeitung, um das redundante container_name-Feld zu entfernen.
Verwandte Beiträge
- React-Anwendungen in der Produktion bereitstellen: Vollständiges Docker-Setup mit Traefik Reverse Proxy
- Multi-Tenant-Entwicklungsstack mit Docker aufbauen
- Traefik Reverse Proxy: Der vollständige Self-Hosting-Leitfaden für HTTPS und SSL-Automatisierung
- n8n auf Hetzner Cloud selbst hosten: Vollständiges Docker-Setup-Tutorial