Beim Arbeiten mit Docker besteht oft der Wunsch, einen Container automatisch bei jedem Rechnerstart auszuführen, z.B. um einen Netzwerkdienst anzubieten. In diesem Text stelle ich Ihnen drei Wege vor, wie Sie diesen Wunsch realisieren können. Die ersten beiden Varianten setzen voraus, dass es einen systemweiten Docker-Dienst gibt (dockerd
), dass Sie also mit einer »normalen« Docker-Installation arbeiten (nicht rootless oder mit mit Podman).
Variante 1: docker run --restart always
Wenn Sie beim Start eines Docker-Containers mit docker run
die Option --restart always
übergeben, dann wird dieser Container in Zukunft automatisch gestartet:
docker run -p 8080:80 --name apache -v "${PWD}":/usr/local/apache2/htdocs -d --restart always httpd
Für die Option --restart
gibt es vier mögliche Einstellungen:
--restart no
gilt standardmäßig. Wenn der Container endet, egal aus welchem Grund, wird er nicht neu gestartet.-
--restart always
bewirkt, dass der Container automatisch neu gestartet wird, sobald er endet. Diese Neustartregel gilt auch für einen Reboot des Rechners! Dabei wird im Rahmen des Init-Prozesses der Docker-Dienst gestartet; dieser startet wiederum alle Container, die zuletzt mit der Option--restart always
liefen. Aber Vorsicht:--restart always
gilt auch für ein Programmende aufgrund eines Fehlers. Wenn der Container einen Fehler enthält, kann es passieren, dass der Container immer wieder gestartet wird. Die einzige Ausnahme ist ein manueller Stopp (docker stop
): In diesem Fall wird der Container nicht unmittelbar neu gestartet. Wenn Sie aber Ihren Rechner herunterfahren, dann wird der Container beim nächsten Docker-Neustart wiederum gestartet. -
--restart unless-stopped
funktioniert ganz ähnlich wie--restart always
. Der Unterschied besteht darin, dass ein mitdocker stop
beendeter Container beim nächsten Docker-Neustart nicht mehr automatisch gestartet wird. -
--restart on-failure
führt zu einem automatischen Neustart nach einem Fehler im Container, aber zu keinem Autostart bei einem Neustart des Docker-Systems.
Das für einen Container eingestellte Restart-Verhalten können Sie mit docker inspect
ermitteln:
docker inspect <containername>
...
"RestartPolicy": {
"Name": "always",
"MaximumRetryCount": 0
},
...
Mit docker update
können Sie das Update-Verhalten eines Containers verändern, während er läuft:
docker update --restart on-failure <containername>
Variante 2: docker-compose-Datei mit der restart-Option
Einen automatischen Neustart können Sie auch in der Datei docker-compose.yml
festschreiben, und zwar mit dem Schlüsselwort restart
:
services:
db:
image: mariadb:latest
restart: always
Die vier zulässigen Einstellungen lauten no
(gilt per Default), always
, unless-stopped
und on-failure
. Die Bedeutung der Schlüsselwörter ist wie bei der vorhin erläuterten Option --restart
von docker run
. Die Einstellung
restart: always
gilt solange, bis die durch die Datei docker-compose.yml
beschriebenen Dienste explizit durch compose down
wieder beendet und gelöscht werden.
Achten Sie darauf, dass Sie das Schlüsselwort restart
in der richtigen Ebene angeben, also direkt bei den Einstellungen des jeweiligen Containers (im vorigen Beispiel db
)! Zuletzt habe ich mich wochenlang gewundert, warum bei der folgenden Datei docker-compose.yml
das Restart-Verhalten nicht funktionierte:
# Vorsicht, die restart-Einstellung ist hier fehlerhaft!
services:
db:
image: mariadb:latest
volumes:
- vol-db:/var/lib/mysql
environment:
MYSQL_USER: wpuser
MYSQL_PASSWORD: geheim
restart: always
Die Fehlerursache sollte klar sein: Die Option restart
ist zu weit eingerückt und bezieht sich nicht auf den Container db
, sondern auf dessen environment
~~Einstellungen. Dort wird restart
einfach ignoriert. Die falsch platzierte Option führt aber zu keiner Warnung oder gar Fehlermeldung.
Variante 3: systemd
Anstatt den Start von Containern durch Docker zu steuern, besteht auch die Möglichkeit, dies durch Funktionen des Betriebssystems zu erledigen. Diese Vorgehensweise ist relativ umständlich und wird in der Praxis daher nur recht selten gewählt. Sie hat aber den Vorteil, dass ein Container wie ein Dienst des Betriebssystem behandelt werden und mit den gleichen Kommandos gesteuert kann.
Ich gehe im Folgenden davon aus, dass Sie eine Linux-Distribution mit systemd verwenden. Sobald die Konfiguration einmal funktioniert, können auf den betreffende Container Kommandos wie systemctl restart
oder systemctl enable --now
angewendet werden.
Der Ausgangspunkt des folgenden Beispiels ist ein MySQL-Container, der zuerst testweise eingerichtet wurde und der nun dauerhaft gestartet werden soll. Das Volume mit den Datenbanken befindet sich in /home/kofler/docker-mysql-volume
. Beim erstmaligen Einrichten des Containers wurde das MySQL-Root-Passwort festgelegt. Es ist in einer Datenbank im Volume-Verzeichnis gespeichert und muss deswegen nicht mehr mit -e MYSQL_ROOT_PASSWORD=...
übergeben werden.
Eine eigene Servicedatei
Damit sich systemd selbstständig um den Start kümmert, muss im Verzeichnis /etc/systemd/system
eine *.service
-Datei eingerichtet werden. Für dieses Beispiel sieht die Datei so aus:
# Datei /etc/systemd/system/docker-mysql.service
[Unit]
Description=starts MySQL server as Docker container
After=docker.service
Requires=docker.service
[Service]
RemainAfterExit=true
ExecStartPre=-/usr/bin/docker stop mysql-db-buch
ExecStartPre=-/usr/bin/docker rm mysql-db-buch
ExecStartPre=-/usr/bin/docker pull mysql
ExecStart=/usr/bin/docker run -d --restart unless-stopped \
-v /home/kofler/docker-mysql-volume:/var/lib/mysql \
-p 13305:3306 --name mysql-db-buch mysql
ExecStop=/usr/bin/docker stop mysql-db-buch
[Install]
WantedBy=multi-user.target
Der Unit
-Abschnitt beschreibt den Dienst. Die Schlüsselwörter Requires
und After
stellen sicher, dass der Dienst nicht vor Docker gestartet wird.
Der Service
-Abschnitt legt fest, welche Aktionen zum Start bzw. zum Stopp des Diensts erforderlich sind. Die drei ExecStartPre
-Anweisungen stellen sicher, dass der Container mit dem Namen mysql-db-buch
gestoppt und gelöscht wird und dass die neueste Version des MySQL-Images heruntergeladen wird. Das Minuszeichen vor den stop
– und rm
-Kommandos bedeutet, dass ein Fehler beim Ausführen dieser Kommandos einfach ignoriert wird. (Wenn das Image bereits zuvor gestoppt und/oder gelöscht wurde, dann ist das kein Grund zur Sorge.)
Die über mehrere Zeilen verteilte ExecStart
-Anweisung startet schließlich den Container, wobei das Volume-Verzeichnis /home/kofler/docker-mysql-volume
und der Host-Port 13306 verwendet werden.
ExecStop
gibt an, was zu tun ist, um den Dienst zu stoppen.
RemainAfterExit=true
bedeutet, dass sich systemd den gerade aktivierten Status merkt. Ohne diese Option würde systemd nach der erfolgreichen Ausführung von docker run
glauben, der Dienst sei bereits wieder beendet. Tatsächlich wird der Container aber im Hintergrund weiter ausgeführt.
WantedBy
im Install
-Abschnitt legt fest, dass der Dienst Teil des Multi-User-Targets sein soll. (Dieses Target beschreibt den normalen Betriebszustand eines Linux-Servers.)
Selbstverständlich können Sie in der Service-Datei anstelle von docker
auch docker compose
aufrufen. Das ist zweckmäßig, wenn der gewünschte Dienst nicht aus nur einem Container besteht, sondern aus einer ganze Gruppe von Containern. Denken Sie daran, dass Sie beim Aufruf von docker compose
den vollständigen Ort der Datei docker-compose.yml
mit der Option -f
explizit angeben müssen.
Den Service starten und beenden
Damit systemd die neue Servicedatei berücksichtigt, müssen Sie einmalig das folgende Kommando ausführen:
systemctl daemon-reload
Sollten Sie in der Servicedatei einen Fehler eingebaut haben und ihn später korrigieren, vergessen Sie nicht, das obige Kommando neuerlich auszuführen!
Mit den folgenden Kommandos testen Sie, ob der manuelle Start und Stopp des Docker-Containers funktioniert:
systemctl start docker-mysql
systemctl status docker-mysql
docker-mysql.service - starts MySQL server as Docker container
Loaded: loaded (/etc/systemd/system/docker-mysql.service;
disabled; vendor preset: enabled)
Active: active (exited) since 19:28:20 CEST; 3min 41s ago
...
systemctl stop docker-mysql
Sofern alles klappt, müssen Sie den automatischen Start des Dienstes nun noch dauerhaft aktivieren:
systemctl enable --now docker-mysql
Sollten Sie den Dienst später nicht mehr brauchen, beenden Sie ihn wie folgt dauerhaft:
systemctl disable --now docker-mysql
Quellen/Links
- https://docs.docker.com/config/containers/start-containers-automatically
- https://docs.docker.com/compose/compose-file/compose-file-v3/#restart
- https://www.digitalocean.com/community/tutorials/understanding-systemd-units-and-unit-files
- https://wiki.archlinux.org/title/Systemd
- https://www.freedesktop.org/wiki/Software/systemd
zu Variante 3:
Kann es da (durch das Autoupdate) nicht zu Problemen kommen, falls mal die Internetverbindung nicht will?
-> docker rm mysql-db-buch
Sollte man nicht das Image nur ersetzen, wenn eine neuere Version verfügbar ist?
;-)
Und was tut man, wenn der neue Container mit dem alten Volume nicht mehr mag?
Könnte man automatisiert ( ohne aufwendige bash Skripte) die alte Version wiederherstellen ?
Werden in Ihrem Docker Buch solche Updateszenarien und Fehlerszenarien behandelt?
thx
Ich habe gerade ein weiteres Minus-Zeichen vor
docker pull
eingebaut, damit nichts passieren kann, wenn gerade keine Verbindung zum Docker Hub möglich ist.docker rm
ist kein Problem: Es löscht ja nur den Container, nicht das Image. Das (alte) Image bleibt bestehen. Wenn es kein Update gibt oder wenn kein Update möglich ist, wird der neue Container auf Basis des alten Images eingerichtet.Eine Volume-Inkompatibilität ist natürlich theoretisch möglich, in der Praxis hatte ich damit aber noch nie Schwierigkeiten. Ich wüsste nicht, wie man dagegen per Script vorgehen kann. Es ist wie mit allen automatisierten Updates: Hundertprozentigen Schutz vor Fehlern gibt es nicht. (Persönlich bin ich der Meinung, dass bei automatisierten Updates die Vorteile dennoch überwiegen, aber da kann man durchaus anderer Meinung sein.)
Zur Frage nach Update- und Fehlerszenarien: Dieser Blog-Beitrag basiert in leicht gekürzter Form auf einem neuen Abschnitt aus der 3. Auflage des Buchs, die demnächst erscheinen wird. Mehr Sonderfälle werden auch im Buch nicht behandelt.