Docker Compose beim Systemstart automatisch starten
So starten Docker-Compose-Dienste nach einem Neustart automatisch: passende restart-Policy wählen, warum on-failure nicht genügt und wann systemd nötig ist.
Die kurze Antwort
Docker Compose-Dienste werden beim Systemstart gestartet, wenn zwei Bedingungen gleichzeitig erfüllt sind. Der Docker-Daemon muss als Systemdienst aktiviert sein, und jeder Dienst in der Datei muss eine Neustartrichtlinie mit unless-stopped oder always enthalten. Fügen Sie restart: unless-stopped zu jedem Dienst hinzu und führen Sie einmal docker compose up -d aus. Danach werden die Container nach einem Neustart automatisch wieder gestartet. Im üblichen Fall ist nichts weiter erforderlich.
Eine systemd-Unit benötigen Sie nur, wenn die Reihenfolge wichtig ist: etwa bei einem Stack, der von einem eingebundenen Datenträger, einer VPN-Schnittstelle oder einer Netzwerkfreigabe abhängt, die beim Start des Docker-Daemons noch nicht bereit ist. Dieser Fall ist relevant und wird in der zweiten Hälfte dieses Leitfadens behandelt. Wenn Sie sich noch mit Dienstdefinitionen und Volumes vertraut machen, beginnen Sie mit den Grundlagen von Docker Compose auf einem VPS und kehren Sie danach hierher zurück.
Die Neustart-Richtlinie in compose.yaml festlegen
Die Richtlinie wird für jeden Service in einer eigenen Zeile festgelegt. Es gibt keinen globalen Schalter. Wenn Sie einen Service vergessen, bleibt er nach dem Neustart heruntergefahren, während der übrige Stack gestartet wird.
services:
app:
image: nginx:1.27
restart: unless-stopped
ports:
- "8080:80"
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_PASSWORD: change-me
volumes:
- dbdata:/var/lib/postgresql/data
volumes:
dbdata:Wenden Sie die Änderung an und lesen Sie die Richtlinie anschließend aus dem laufenden Container aus:
docker compose up -d
docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app)Der Befehl gibt unless-stopped aus. Wenn no ausgegeben wird, wurde die Datei bearbeitet, der Container aber nie neu erstellt.
Dies ist der häufigste Fehler. Die Neustart-Richtlinie wird im Container gespeichert, nicht in der YAML-Datei. Das Bearbeiten von compose.yaml ändert nichts an einem bereits vorhandenen Container. Auch docker compose restart hilft nicht, da derselbe Container angehalten und wieder gestartet wird, ohne seine Konfiguration zu ändern. Nur docker compose up -d vergleicht die Datei mit den laufenden Containern, erkennt die geänderte Richtlinie und erstellt die Container neu.
Wenn Sie einen Container derzeit nicht neu erstellen möchten, ändern Sie die Richtlinie direkt:
docker update --restart unless-stopped my-containerBearbeiten Sie trotzdem auch die YAML-Datei. docker update ändert den laufenden Container. Beim nächsten docker compose up -d wird die Datei erneut eingelesen und der alte Wert wiederhergestellt.
Was die einzelnen Restart-Werte tatsächlich bewirken
Docker definiert vier Werte. Der Unterschied zwischen ihnen zeigt sich erst beim Neustart des Rechners oder des Daemons.
noist der Standardwert. Der Container wird unter keinen Umständen automatisch neu gestartet.alwaysstartet den Container jedes Mal neu, wenn er beendet wird. Wenn Sie ihn manuell gestoppt haben, wird er beim nächsten Start des Docker-Daemons trotzdem wieder gestartet. Das ist oft überraschend: Ein Container, den Sie letzte Woche absichtlich gestoppt haben, läuft nach einem Neustart wieder.unless-stoppedverhält sich wiealways. Ein manuell gestoppter Container bleibt jedoch auch nach einem Neustart des Daemons gestoppt. Diesen Wert verwenden Sie für einen Dienst, den Sie gelegentlich für Wartungsarbeiten herunterfahren.on-failurestartet den Container nur neu, wenn er mit einem Exit-Code ungleich 0 beendet wird. Sie können die Anzahl der Versuche begrenzen, wie inrestart: on-failure:3.
Für einen Stack, der immer laufen soll, wenn der Server läuft, ist unless-stopped der richtige Standardwert. Wählen Sie always nur, wenn ein Container nicht dauerhaft gestoppt bleiben soll.
Warum Neustarts nicht über einen Reboot hinweg bestehen bleiben: on-failure
Viele wählen on-failure, weil diese Option umsichtig klingt. Danach stellen sie fest, dass nach dem ersten Reboot alle Container gestoppt sind. Der Grund ergibt sich aus der Definition. on-failure reagiert nur auf ein Ereignis: Der Containerprozess wird mit einem Fehlercode beendet.
Ein Reboot ist kein Fehler. Beim Herunterfahren des Hosts stoppt systemd docker.service, und der Daemon stoppt jeden Container absichtlich. Der Container ist nicht fehlgeschlagen. Daher gibt es für die Richtlinie keine Reaktion. Beim anschließenden Hochfahren prüft der Daemon, welche Container fortgesetzt werden müssen. Ein on-failure-Container, der ordnungsgemäß gestoppt wurde, gehört nicht dazu. Er bleibt im Status exited.
Sie können dies direkt prüfen. Setzen Sie restart: on-failure für einen Service, führen Sie docker compose up -d aus, starten Sie den Host neu und führen Sie anschließend Folgendes aus:
docker compose ps -aDer Service wird mit dem Status Exited und einem Status wie Exited (0) 2 minutes ago aufgeführt. Es ist nichts defekt, und es wird kein Fehler protokolliert. Das erschwert die Diagnose. Die Richtlinie hat genau so funktioniert, wie sie definiert ist.
on-failure ist weiterhin nützlich. Die Option eignet sich für einen Container, der einen Job ausführt und abstürzen kann, wenn Sie eine begrenzte Anzahl von Wiederholungen und keine Neustartschleife wünschen. Für einen dauerhaft laufenden Service, der Reboots überstehen soll, ist sie das falsche Werkzeug.
Neustartrichtlinien funktionieren nur, wenn der Docker-Dienst beim Systemstart gestartet wird
Neustartrichtlinien werden vom Docker-Daemon durchgesetzt. Wenn der Daemon nicht startet, setzt sie nichts durch. Prüfen Sie dies:
systemctl is-enabled docker
systemctl is-enabled containerdBeide Befehle sollten enabled ausgeben. Die Pakete aus dem offiziellen Docker-Repository aktivieren die Dienste bei der Installation. Auf einem neuen Server ist diese Prüfung daher normalerweise erfolgreich. Wenn einer der beiden Befehle disabled ausgibt, korrigieren Sie die Konfiguration:
sudo systemctl enable --now docker containerdHier gibt es eine wichtige Besonderheit. Ubuntu liefert auch docker.socket aus. Dieser Dienst startet den Daemon bei Bedarf, sobald erstmals etwas mit der Docker-API kommuniziert. docker.socket ist aktiviert, und man geht deshalb davon aus, dass der Daemon abgedeckt ist. Dann wird docker.service deaktiviert, um Speicher zu sparen. Beim Systemstart ruft nichts die API auf. Der Socket wird daher nie verwendet, der Daemon startet nicht, und kein Container wird gestartet, bis Sie den ersten Befehl mit docker eingeben. Die Socket-Aktivierung ersetzt nicht die Aktivierung von docker.service.
Wann eine systemd-Unit die bessere Lösung ist
Neustartrichtlinien berücksichtigen keine Reihenfolge im Verhältnis zum übrigen System. Der Daemon startet und fährt Ihre Container so schnell wie möglich hoch. Wenn Ihr Stack ein Verzeichnis aus einem separaten Volume, einer NFS-Freigabe (Network File System) oder einer verschlüsselten Festplatte per Bind-Mount einbindet, starten die Container möglicherweise, bevor dieser Pfad vorhanden ist. Docker erstellt am Einhängepunkt problemlos ein leeres Verzeichnis und startet den Container damit. Ihre Datenbank startet dann ohne Daten.
Schreiben Sie eine systemd-Unit, wenn einer dieser Punkte zutrifft. Der Stack benötigt zuerst einen Mount, eine VPN-Schnittstelle oder eine andere Unit. Sie möchten, dass systemctl stop myapp und systemctl start myapp wie bei jedem anderen Dienst auf dem System funktionieren. Oder Sie möchten, dass der Stack beim Herunterfahren ordnungsgemäß beendet wird, anstatt zusammen mit dem Daemon beendet zu werden. Wenn systemd-Units für Sie neu sind, behandelt eine systemd-Service-Unit und einen Timer schreiben das Dateiformat ausführlicher.
Die systemd-Unit schreiben
Legen Sie den Stack in einem festen Pfad außerhalb eines Home-Verzeichnisses ab. /srv/myapp ist dafür eine gute Wahl, weil eine Unit, die ausgeführt wird, bevor sich jemand anmeldet, /home nicht lesen muss.
Erstellen Sie /etc/systemd/system/myapp.service:
[Unit]
Description=myapp docker compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
RequiresMountsFor=/srv/myapp/data
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/srv/myapp
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.targetAktivieren und starten Sie die Unit:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
systemctl status myapp.serviceEine funktionsfähige Unit zeigt Active: active (exited) an. Beim ersten Mal wirkt das falsch. Es ist korrekt: Type=oneshot zusammen mit RemainAfterExit=yes bedeutet, dass die Unit ihren Befehl ausgeführt hat, der Befehl beendet wurde und systemd die Unit als aktiv markiert lässt, damit ExecStop beim Herunterfahren ausgeführt wird.
Jede Zeile hat einen Zweck. Requires=docker.service bedeutet, dass die Unit sofort fehlschlägt, statt docker compose gegen einen nicht verfügbaren Socket auszuführen. After= legt die Reihenfolge fest, weil Requires= allein dies nicht tut. RequiresMountsFor= sorgt dafür, dass systemd die Mount-Unit für diesen Pfad einbindet und darauf wartet. Genau deshalb verwenden Sie eine Unit statt einer Restart-Richtlinie. TimeoutStartSec=0 verhindert, dass systemd den Startauftrag beendet, während noch ein großes Image heruntergeladen wird.
Ein Hinweis zur Kombination der beiden Mechanismen. Die Dokumentation von Docker rät davon ab, Restart-Richtlinien mit einem Prozessmanager des Hosts zu kombinieren. Diese Warnung bezieht sich auf einen Prozessmanager, der den Containerprozess selbst überwacht und neu startet, während der Daemon dasselbe versucht. Eine Type=oneshot-Unit überwacht nichts. Daher ist es unproblematisch, restart: unless-stopped zusammen mit dieser Unit in der Compose-Datei beizubehalten. Genau das ist hier gewünscht. systemd steuert die Reihenfolge beim Booten, und der Daemon behandelt einen Container, der um drei Uhr morgens abstürzt.
Mit einem echten Neustart prüfen
Der echte Test ist durch nichts zu ersetzen. systemctl restart docker prüft die Reihenfolge beim Einhängen nicht, und docker compose down gefolgt von docker compose up -d prüft den Systemstart überhaupt nicht.
sudo rebootWarten Sie, stellen Sie die Verbindung wieder her und prüfen Sie in dieser Reihenfolge:
uptime
systemctl is-active docker
docker compose psuptime bestätigt, dass Sie eine Maschine betrachten, die tatsächlich neu gestartet wurde. docker compose ps sollte, aus dem Stack-Verzeichnis ausgeführt, jeden Dienst mit dem Status running und einer Uptime auflisten, die ungefähr der Uptime der Maschine entspricht. Ein Dienst mit dem Status Exited ist der Dienst, den Sie prüfen sollten.
Wenn ein Dienst nicht gestartet wurde, enthält das Daemon-Log den Zeitraum des Systemstarts:
journalctl -u docker.service -b --no-pager | tail -50Bei einem Stack, der von einer Unit verwaltet wird, zeigt journalctl -u myapp.service -b --no-pager die genaue Ausgabe von docker compose beim Systemstart, einschließlich eines fehlgeschlagenen Image-Pulls oder einer fehlenden .env-Datei.
Dinge, die den automatischen Start unbemerkt verhindern
Container, die mit docker compose run erstellt wurden, übernehmen die Neustartrichtlinie aus der Datei nicht. Compose behandelt sie als einmalige Container. Wenn ein Dienst seine Richtlinie scheinbar ignoriert, prüfen Sie, ob er mit run statt mit up gestartet wurde.
Ein relativer Pfad in einem Volume oder in einem Eintrag env_file wird relativ zum Verzeichnis der Compose-Datei aufgelöst. Das funktioniert in Ihrer Shell und auch über eine Unit, die WorkingDirectory setzt. Über eine Unit ohne diese Einstellung schlägt es fehl, weil das Arbeitsverzeichnis dann / ist.
Rootless Docker ist ein Sonderfall. Der Daemon läuft als Benutzerdienst. Ein Benutzerdienst wird beendet, sobald die letzte Sitzung dieses Benutzers endet. Aktivieren Sie ihn für den Benutzer und erlauben Sie ihm, ohne angemeldeten Benutzer weiterzulaufen:
systemctl --user enable docker
sudo loginctl enable-linger $USEROhne enable-linger wird der Rootless-Daemon beim Abmelden beendet, und die Container werden ebenfalls beendet. Das sieht genau wie eine fehlerhafte Neustartrichtlinie aus.
Noch ein Punkt: Automatische Sicherheitsupdates können einen Server zu einer festgelegten Uhrzeit neu starten. Das ist nur dann sinnvoll, wenn Ihr Stack selbstständig wieder startet. Die Einrichtung auf einer neuen Maschine gehört zusammen mit der übrigen Arbeit der ersten Stunde in die ersten zehn Minuten auf einem neuen VPS.
FAQ
Was ist der Unterschied zwischen restart: always und restart: unless-stopped?
Beide starten den Container neu, wenn er selbstständig beendet wird. Der Unterschied zeigt sich, nachdem Sie einen Container manuell beendet haben. Mit always startet der Container beim nächsten Start des Docker-Daemons erneut. Ein Neustart macht das manuelle Beenden daher rückgängig. Mit unless-stopped merkt sich der Daemon, dass der Container absichtlich beendet wurde, und lässt ihn beendet. Verwenden Sie unless-stopped, sofern der Container nicht dauerhaft beendet bleiben soll.
Ich habe restart: unless-stopped hinzugefügt, aber der Container startet nach einem Neustart weiterhin nicht. Warum?
Die Richtlinie ist am Container hinterlegt, nicht in der Datei. Ein bereits vorhandener Container wird durch das Bearbeiten der YAML-Datei nicht aktualisiert. Führen Sie docker compose up -d aus, damit Compose den Container neu erstellt. Prüfen Sie anschließend mit docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app). Wenn dabei no ausgegeben wird, wurde der Container vor Ihrer Änderung erstellt. Eine weitere häufige Ursache ist, dass docker.service nicht aktiviert ist. Das können Sie mit systemctl is-enabled docker prüfen.
Benötige ich eine systemd-Unit, wenn ich bereits Neustartrichtlinien verwende?
Normalerweise nicht. Eine Neustartrichtlinie reicht für einen Stack aus, der nur das Netzwerk benötigt. Das trifft auf die meisten Stacks zu. Fügen Sie eine Unit hinzu, wenn die Container von etwas abhängen, das beim Start des Docker-Daemons noch nicht bereit ist, etwa von einem externen Datenträger, einem verschlüsselten Volume, einer NFS-Freigabe oder einer VPN-Schnittstelle. Die Unit legt mithilfe von After= und RequiresMountsFor= eine Startreihenfolge fest. Eine Neustartrichtlinie kann diese Abhängigkeit nicht ausdrücken.
Wie beende ich einen Stack dauerhaft, ohne dass er beim nächsten Neustart wieder gestartet wird?
Mit unless-stopped reicht docker compose stop aus, da ein manuell beendeter Container beim Neustart des Daemons nicht wieder gestartet wird. Mit always reicht das Beenden nicht aus. Der Container wird nach einem Neustart erneut gestartet. Führen Sie entweder docker compose down aus, um die Container zu entfernen, oder ändern Sie die Richtlinie zuerst mit docker update --restart no my-container. Wenn eine systemd-Unit den Stack verwaltet, führen Sie zusätzlich sudo systemctl disable myapp.service aus. Andernfalls startet die Unit den Stack erneut.