Docker-Container automatisch starten

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 mit docker 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

2 Gedanken zu „Docker-Container automatisch starten“

  1. 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

    1. 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.

Kommentare sind geschlossen.