Docker Compose beim Booten automatisch starten
So starten Docker-Compose-Dienste nach einem Reboot: Docker-Daemon aktivieren, restart: unless-stopped setzen und verstehen, warum on-failure nicht genügt.
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 mit unless-stopped oder always enthalten. Fügen Sie jedem Dienst restart: unless-stopped hinzu, führen Sie einmal docker compose up -d aus, und die Container starten nach einem Reboot selbstständig. Im üblichen Fall ist nichts weiter erforderlich.
Eine systemd-Unit benötigen Sie nur, wenn die Reihenfolge relevant 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 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 in einer eigenen Zeile festgelegt. Es gibt keinen globalen Schalter. Wenn Sie einen Dienst vergessen, bleibt er nach dem Reboot beendet, 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 anschließend die Richtlinie des laufenden Containers 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. Änderungen an compose.yaml wirken sich nicht auf einen bereits vorhandenen Container aus. docker compose restart hilft ebenfalls nicht, weil 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 jetzt nicht neu erstellen möchten, ändern Sie die Richtlinie direkt:
docker update --restart unless-stopped my-containerBearbeiten Sie die YAML-Datei trotzdem ebenfalls. docker update ändert den laufenden Container, und der nächste docker compose up -d liest die Datei ein und setzt den alten Wert wieder ein.
Was die einzelnen Restart-Werte tatsächlich bewirken
Docker definiert vier Werte. Der Unterschied zwischen ihnen zeigt sich erst, wenn der Rechner neu startet 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 beendet haben, wird er beim nächsten Start des Docker-Daemons trotzdem wieder gestartet. Das ist häufig überraschend: Ein Container, den Sie letzte Woche absichtlich beendet haben, läuft nach einem Reboot wieder.unless-stoppedverhält sich wiealways. Ein manuell beendeter Container bleibt jedoch auch nach einem Neustart des Daemons beendet. Diesen Wert verwenden Sie für einen Dienst, den Sie gelegentlich für Wartungsarbeiten anhalten.on-failurestartet den Container nur neu, wenn er mit einem Exit-Code ungleich null 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 auch dann nicht dauerhaft beendet bleiben soll, wenn er einmal angehalten wurde.
Warum restart: on-failure einen Reboot nicht übersteht
Viele wählen on-failure, weil es vorsichtig klingt. Nach dem ersten Reboot stellen sie dann fest, dass jeder Container gestoppt ist. Der Grund liegt in der Definition. on-failure reagiert nur auf eines: Wenn der Containerprozess 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 Richtlinie keinen Anlass zu reagieren. 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 das direkt prüfen. 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 aufgeführt. Es ist nichts beschädigt, und es wird kein Fehler protokolliert. Das erschwert die Diagnose. Die Richtlinie hat genau das getan, was sie vorgibt.
on-failure ist weiterhin nützlich. Es eignet sich für einen Container, der einen Auftrag ausführt und abstürzen kann. restart: on-failure setzt allein kein Limit und startet einen Auftrag, der wiederholt fehlschlägt, immer wieder neu. Fügen Sie daher beispielsweise restart: on-failure:3 hinzu, wenn Sie eine begrenzte Anzahl von Wiederholungsversuchen und keine Neustartschleife wünschen. Für einen dauerhaft laufenden Dienst, der Reboots überstehen soll, ist dies das falsche Werkzeug.
Neustart-Richtlinien funktionieren nur, wenn der Docker-Dienst beim Systemstart gestartet wird
Neustart-Richtlinien werden vom Docker-Daemon durchgesetzt. Wenn der Daemon nicht startet, wird nichts durchgesetzt. Prüfen Sie dies:
systemctl is-enabled docker
systemctl is-enabled containerdBeide Befehle sollten enabled ausgeben. Die Pakete aus dem offiziellen Docker-Repository aktivieren diese Dienste bei der Installation. Auf einem neuen Server ist diese Prüfung daher normalerweise erfolgreich. Wenn einer der beiden Befehle disabled ausgibt, korrigieren Sie dies:
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. Häufig wird docker.socket als aktiviert angezeigt. Deshalb nehmen manche an, dass der Daemon damit abgedeckt ist, und deaktivieren docker.service, um Speicher zu sparen. Beim Systemstart greift dann nichts auf die API zu. Der Socket wird nicht verwendet, der Daemon startet nicht, und kein Container wird gestartet, bis Sie erstmals einen docker-Befehl eingeben. Die 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 dem restlichen System. Der Daemon startet und fährt Ihre Container so früh 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 wird dann ohne Daten gestartet.
Erstellen Sie eine systemd-Unit, wenn einer dieser Punkte 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 ordnungsgemäß beendet wird, anstatt zusammen mit dem Daemon beendet zu werden. Wenn systemd-Units für Sie neu sind, erläutert eine systemd-Service-Unit und einen Timer erstellen das Dateiformat ausführlicher.
Erstellen der systemd-Unit
Legen Sie den Stack unter einem festen Pfad außerhalb eines Home-Verzeichnisses ab. /srv/myapp ist eine gute Wahl, weil eine Unit, die vor der Anmeldung eines Benutzers ausgeführt wird, 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 fehlerfreie Unit zeigt Active: active (exited). Beim ersten Anblick wirkt das 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 Zweck. Requires=docker.service bedeutet, dass die Unit sofort fehlschlägt, statt docker compose für einen nicht erreichbaren 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 darauf wartet. Genau deshalb wird eine Unit statt einer Restart-Richtlinie verwendet. TimeoutStartSec=0 verhindert, dass systemd den Startvorgang beendet, während noch ein großes Image abgerufen wird.
Ein Hinweis zur Kombination der beiden Mechanismen. Die Docker-Dokumentation rät davon ab, Restart-Richtlinien mit einem Prozessmanager auf Hostebene zu kombinieren, und nennt als Grund lediglich mögliche Konflikte. Der Konflikt entsteht durch 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 unproblematisch, restart: unless-stopped neben dieser Unit in der Compose-Datei beizubehalten. Genau das ist gewünscht. systemd übernimmt die Reihenfolge beim Booten. Der Daemon kümmert sich um 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. Deshalb muss systemds eigenes Restart= die Überwachung übernehmen. dsh headless hinter systemd ausführen ist ein vollständig ausgearbeitetes Beispiel für diesen Aufbau, einschließlich des dedizierten Benutzers und des Journals.
Mit einem echten Reboot prüfen
Der echte Test ist durch nichts zu ersetzen. systemctl restart docker prüft die Reihenfolge der Mounts nicht, und docker compose down gefolgt von docker compose up -d prüft überhaupt nichts, was den Bootvorgang betrifft.
sudo rebootWarten Sie, verbinden Sie sich erneut 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, ausgeführt aus dem Stack-Verzeichnis, sollte 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 derjenige, den Sie prüfen sollten.
Wenn ein Dienst nicht gestartet wurde, enthält das Daemon-Log den relevanten Zeitraum des Bootvorgangs:
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 vollständige docker compose-Ausgabe seit dem Bootvorgang, einschließlich eines fehlgeschlagenen Image-Pulls oder einer fehlenden .env-Datei. Den geplanten Reboot beobachten Sie selbst. Lassen Sie sich daher von der Unit über die Reboots informieren, die Sie nicht beobachten: Eine OnFailure=-Zeile, die auf einen selbst gehosteten ntfy-Server verweist, macht aus einem Stack, der nicht zurückgekehrt ist, eine Push-Benachrichtigung, statt etwas, das Sie 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 env_file-Eintrag 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 Variable 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 genauso aus wie eine fehlerhafte Restart-Richtlinie.
Noch ein letzter Punkt. Automatische Sicherheitsupdates können einen Server zu einer festen Uhrzeit neu starten. Das ist nur dann sinnvoll, wenn Ihr Stack anschließend selbstständig wieder startet. Die Einrichtung auf einer neuen Maschine gehört zusammen mit den übrigen Arbeiten 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 Richtlinien starten den Container neu, wenn er selbstständig beendet wird. Der Unterschied zeigt sich, wenn Sie einen Container von Hand stoppen. Bei always startet der Container beim nächsten Start des Docker-Daemons erneut. Ein Reboot macht Ihren manuellen Stopp damit rückgängig. Bei unless-stopped merkt sich der Daemon, dass der Container absichtlich gestoppt wurde, und lässt ihn gestoppt. Verwenden Sie unless-stopped, es sei denn, Sie möchten ausdrücklich, dass ein Container nach einem Neustart wieder gestartet wird.
Ich habe restart: unless-stopped hinzugefügt, aber der Container startet nach einem Reboot weiterhin nicht. Warum?
Die Richtlinie gilt für den Container, nicht für die 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 dort 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 mit After= und RequiresMountsFor= eine Startreihenfolge, die eine Restart-Richtlinie nicht abbilden kann.
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 von Hand 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. Dadurch werden die Container entfernt. 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 ihn erneut.