Docker Compose beim Booten automatisch starten
So starten Compose-Dienste nach einem Reboot: restart: unless-stopped oder always, warum on-failure nicht genügt und wann eine systemd-Unit nötig ist.
Die kurze Antwort
Docker-Compose-Dienste starten beim Booten, wenn zwei Bedingungen gleichzeitig erfüllt sind. Der Docker-Daemon muss als Systemdienst aktiviert sein, und jeder Dienst in der Datei muss eine Restart-Richtlinie von unless-stopped oder always haben. Fügen Sie jedem Dienst restart: unless-stopped hinzu und führen Sie einmal docker compose up -d aus. Danach starten die Container nach einem Reboot automatisch wieder. Für den üblichen Fall ist nichts weiter erforderlich.
Eine systemd-Unit benötigen Sie nur, wenn die Reihenfolge eine Rolle spielt: 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 tritt tatsächlich auf und wird in der zweiten Hälfte dieser Anleitung behandelt. Wenn Sie sich mit Dienstdefinitionen und Volumes noch nicht sicher auskennen, beginnen Sie mit den Grundlagen von Docker Compose auf einem VPS und kehren Sie anschließend hierher zurück.
Restart-Richtlinie in compose.yaml festlegen
Die Richtlinie wird für jeden Dienst separat angegeben. Es gibt keinen globalen Schalter. Ein Dienst, den Sie vergessen, bleibt daher nach dem Reboot gestoppt, während der restliche 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)Das gibt unless-stopped aus. Wenn no ausgegeben wird, wurde die Datei bearbeitet, aber der Container nicht neu erstellt.
Dies ist der häufigste Fehler. Die Restart-Richtlinie wird im Container gespeichert, nicht in der YAML-Datei. Das Bearbeiten von compose.yaml ändert nichts an einem bereits vorhandenen Container. docker compose restart hilft ebenfalls nicht, weil derselbe Container beendet 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 momentan nicht neu erstellen möchten, ändern Sie die Richtlinie direkt:
docker update --restart unless-stopped my-containerBearbeiten Sie zusätzlich die YAML-Datei. docker update ändert den laufenden Container, und der nächste docker compose up -d liest die Datei ein und setzt den alten Wert wieder.
Was die einzelnen Restart-Werte tatsächlich bewirken
Docker definiert vier Werte. Der Unterschied zwischen ihnen zeigt sich erst, wenn der Rechner neu gestartet oder der Daemon neu gestartet wird.
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 Reboot 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 zu Wartungszwecken anhalten.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, sobald der Server läuft, ist unless-stopped der richtige Standardwert. Wählen Sie always nur, wenn ein Container nicht dauerhaft gestoppt bleiben soll.
Warum restart: on-failure einen Reboot nicht übersteht
Viele wählen on-failure, weil es vorsichtig klingt, und stellen dann fest, dass nach dem ersten Reboot jeder Container gestoppt ist. Der Grund liegt in der Definition. on-failure reagiert nur auf einen Vorgang: den Prozess des Containers, der mit einem Fehlercode beendet wird.
Ein Reboot ist kein Fehler. Beim Herunterfahren des Hosts stoppt systemd docker.service, und der Daemon beendet jeden Container absichtlich. Der Container ist nicht fehlgeschlagen. Daher gibt es für die Policy keinen Anlass zu reagieren. Beim erneuten 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 das direkt nachvollziehen. Setzen Sie restart: on-failure für einen Dienst, 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 Dienst wird mit dem Status Exited und einem Status wie Exited (0) 2 minutes ago angezeigt. Es ist nichts beschädigt, und es wird kein Fehler protokolliert. Dadurch ist die Ursache schwer zu erkennen. Die Policy hat genau das getan, was sie vorgibt.
on-failure ist weiterhin nützlich. Es eignet sich für einen Container, der einen Job ausführt und abstürzen kann, wenn Sie eine begrenzte Anzahl von Wiederholungsversuchen und keine Neustartschleife wünschen. Für das dauerhafte Ausführen eines Dienstes über Reboots hinweg ist es das falsche Werkzeug.
Neustart-Richtlinien funktionieren nur, wenn der Docker-Dienst beim Booten startet
Neustart-Richtlinien werden vom Docker-Daemon durchgesetzt. Wenn der Daemon nicht startet, setzt sie niemand durch. Prüfen Sie den Status:
systemctl is-enabled docker
systemctl is-enabled containerdBeide Befehle sollten enabled ausgeben. Die Pakete aus dem offiziellen Docker-Repository aktivieren diese Einträge bei der Installation. Auf einem neuen Server ist die 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 ein Prozess mit der Docker-API kommuniziert. Manche Administratoren sehen, dass docker.socket aktiviert ist, gehen deshalb davon aus, dass der Daemon abgedeckt ist, und deaktivieren docker.service, um Speicher zu sparen. Beim Booten kommuniziert dann kein Prozess mit der API. Der Socket wird nicht angesprochen, der Daemon startet nicht, und kein Container wird gestartet, bis Sie erstmals einen docker-Befehl ausführen. Socket-Aktivierung ersetzt nicht die Aktivierung von docker.service.
Wenn eine systemd-Unit die bessere Lösung ist
Neustartregeln berücksichtigen keine Reihenfolge gegenüber anderen Systemkomponenten. Der Daemon startet und fährt Ihre Container so bald wie möglich hoch. Bindet Ihr Stack ein Verzeichnis aus einem separaten Volume, einer NFS-Freigabe (Network File System) oder von einem verschlüsselten Datenträger ein, starten die Container möglicherweise, bevor dieser Pfad vorhanden ist. Docker erstellt am Mountpoint problemlos ein leeres Verzeichnis und startet den Container damit. Ihre Datenbank wird dann ohne Daten gestartet.
Schreiben Sie eine systemd-Unit, wenn einer dieser Fälle zutrifft. Der Stack benötigt einen Mount, eine VPN-Schnittstelle oder eine andere Unit, die zuerst bereit sein muss. 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 sauber beendet wird, statt zusammen mit dem Daemon beendet zu werden. Wenn systemd-Units neu für Sie sind, erläutert eine systemd-Unit und einen Timer schreiben das Dateiformat ausführlicher.
Erstellen der systemd-Unit
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 vor der Anmeldung eines Benutzers startet, keinen Grund hat, /home zu lesen.
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 funktionierende Unit zeigt Active: active (exited) an. Beim ersten Anblick wirkt das möglicherweise falsch. Es ist korrekt: Type=oneshot 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 bestimmten Zweck. Requires=docker.service sorgt dafür, 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 das nicht tut. RequiresMountsFor= sorgt dafür, dass systemd die Mount-Unit für diesen Pfad einbindet und auf sie wartet. Genau deshalb verwenden Sie eine Unit statt einer Restart-Richtlinie. TimeoutStartSec=0 verhindert, dass systemd den Startvorgang beendet, während noch ein großes Image heruntergeladen wird.
Ein Hinweis zur Kombination der beiden Mechanismen. Die Docker-Dokumentation 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 ihn neu startet, während der Daemon dasselbe versucht. Eine Type=oneshot-Unit überwacht nichts. Daher ist es in Ordnung, restart: unless-stopped zusammen mit dieser Unit in der Compose-Datei zu belassen. Genau das ist hier gewünscht. systemd übernimmt die Reihenfolge beim Booten, und der Daemon behandelt einen Container, der um drei Uhr morgens abstürzt.
Die Unit sieht anders aus, wenn ein normaler, dauerhaft laufender Prozess aktiv gehalten werden soll und kein Stack. In diesem Fall gibt es keinen darunterliegenden Daemon, und die eigene Restart=-Funktion von systemd muss die Überwachung übernehmen. dsh ohne Terminal hinter systemd ausführen zeigt dieses Modell anhand eines vollständigen Beispiels, einschließlich des dedizierten Benutzers und des Journals.
Neustart unter realen Bedingungen prüfen
Der reale Test ist nicht ersetzbar. systemctl restart docker prüft die Reihenfolge beim Einhängen von Mounts nicht. Auch docker compose down gefolgt von docker compose up -d prüft nichts, was den Bootvorgang betrifft.
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 prüfen, 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 sollte zuerst geprüft werden.
Wenn ein Dienst nicht gestartet wurde, enthält das Daemon-Log die relevanten Meldungen aus dem Boot-Zeitraum:
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 exakte docker compose-Ausgabe vom Bootvorgang, einschließlich eines fehlgeschlagenen Image-Pulls oder einer fehlenden .env-Datei. Der geplante Neustart ist der Neustart, den Sie überwachen. Lassen Sie sich daher von der Unit über die Neustarts informieren, die Sie nicht selbst beobachten: Eine auf einen selbst gehosteten ntfy-Server gerichtete OnFailure=-Zeile wandelt einen Stack, der nicht zurückgekehrt ist, in eine Push-Benachrichtigung um, statt dass Sie das Problem erst Tage später entdecken.
Dinge, die den automatischen Start unbemerkt verhindern
Mit docker compose run erstellte Container übernehmen die Restart-Richtlinie aus der Datei nicht. Compose behandelt sie als einmalig gestartete 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 von env_file wird relativ zum Verzeichnis der Compose-Datei aufgelöst. Das funktioniert in Ihrer Shell und in einer Unit, die WorkingDirectory setzt. In einer 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, auch 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 Restart-Richtlinie aus.
Noch ein letzter 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 einem neuen Rechner gehört zusammen mit den übrigen Aufgaben 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, wenn Sie einen Container manuell stoppen. Bei always wird der Container beim nächsten Start des Docker-Daemons erneut gestartet. Ein Reboot macht den manuellen Stopp dadurch rückgängig. Bei unless-stopped merkt sich der Daemon, dass der Container absichtlich gestoppt wurde, und lässt ihn beendet. Verwenden Sie unless-stopped, sofern der Container nicht ausdrücklich dauerhaft beendet bleiben soll.
Ich habe restart: unless-stopped hinzugefügt, aber der Container startet nach einem Reboot weiterhin nicht. Warum?
Die Richtlinie ist im Container und nicht in der Datei gespeichert. 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 restart-Richtlinien verwende?
In der Regel nicht. Eine restart-Richtlinie 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 ermöglicht die Reihenfolge über After= und RequiresMountsFor=. Eine restart-Richtlinie kann diese Abhängigkeiten nicht ausdrücken.
Wie stoppe ich einen Stack dauerhaft, ohne dass er beim nächsten Reboot wieder gestartet wird?
Bei unless-stopped reicht docker compose stop aus, weil ein manuell gestoppter Container beim Neustart des Daemons nicht wieder gestartet wird. Bei always reicht ein Stopp nicht aus. Der Container wird nach einem Reboot erneut gestartet. Führen Sie entweder docker compose down aus, wodurch die Container entfernt werden, oder ändern Sie zuerst die Richtlinie 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.