Docker Compose : démarrer les services au boot
Faites redémarrer vos services Docker Compose après un reboot : policies restart, limite de on-failure et cas où une unité systemd devient nécessaire.
Réponse courte
Les services Docker Compose démarrent au boot lorsque deux conditions sont réunies. Le daemon Docker doit être activé comme service système, et chaque service du fichier doit avoir une restart policy définie sur unless-stopped ou always. Ajoutez restart: unless-stopped à chaque service, exécutez docker compose up -d une fois, puis les conteneurs redémarreront automatiquement après un reboot. Rien d’autre n’est nécessaire dans le cas courant.
Vous avez besoin d’une unité systemd uniquement lorsque l’ordre de démarrage est important : par exemple, pour une stack qui dépend d’un disque monté, d’une interface VPN ou d’un partage réseau qui n’est pas encore prêt au moment où le daemon Docker démarre. Ce cas est réel, et la seconde partie de ce guide le couvre. Si vous découvrez encore les définitions de services et les volumes, commencez par les bases de Docker Compose sur un VPS, puis revenez ici.
Définir la policy de redémarrage dans compose.yaml
La policy se définit une ligne par service. Il n’existe pas de réglage global. Un service oublié restera donc arrêté après le redémarrage, tandis que le reste de la stack démarrera.
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:Appliquez la configuration, puis relisez la policy du conteneur en cours d’exécution :
docker compose up -d
docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app)Cette commande affiche unless-stopped. Si elle affiche no, le fichier a été modifié, mais le conteneur n’a jamais été recréé.
C’est le problème le plus fréquent. La restart policy est stockée dans le conteneur, pas dans le fichier YAML. Modifier compose.yaml ne change rien pour un conteneur qui existe déjà. docker compose restart ne suffit pas non plus, car cette commande arrête et redémarre le même objet conteneur sans modifier sa configuration. Seule la commande docker compose up -d compare le fichier aux conteneurs en cours d’exécution, détecte la modification de la policy et recrée les conteneurs.
Pour un conteneur que vous ne souhaitez pas recréer maintenant, modifiez la policy directement :
docker update --restart unless-stopped my-containerModifiez également le fichier YAML. docker update modifie le conteneur en cours d’exécution, et la prochaine commande docker compose up -d relira le fichier et rétablira l’ancienne valeur.
Ce que fait réellement chaque valeur de redémarrage
Docker définit quatre valeurs. Leur différence n’apparaît qu’au redémarrage de la machine ou du daemon.
noest la valeur par défaut. Le conteneur n’est jamais redémarré automatiquement, quelles que soient les circonstances.alwaysredémarre le conteneur chaque fois qu’il s’arrête. Si vous l’avez arrêté manuellement, il redémarre malgré tout au prochain démarrage du daemon Docker. C’est souvent surprenant : un conteneur que vous aviez volontairement arrêté la semaine précédente est de nouveau en fonctionnement après un redémarrage.unless-stoppedse comporte commealways, sauf qu’un conteneur arrêté manuellement reste arrêté après le redémarrage du daemon. C’est la valeur à utiliser pour un service que vous arrêtez parfois pour effectuer la maintenance.on-failureredémarre le conteneur uniquement lorsqu’il se termine avec un code de sortie différent de zéro. Vous pouvez limiter le nombre de tentatives, comme dansrestart: on-failure:3.
Pour une stack qui doit simplement fonctionner lorsque le serveur fonctionne, unless-stopped est la bonne valeur par défaut. Choisissez always uniquement si vous voulez empêcher qu’un conteneur reste arrêté.
Pourquoi restart: on-failure ne survit pas à un redémarrage
De nombreuses personnes choisissent on-failure parce que cette option semble prudente, puis constatent que tous les conteneurs sont arrêtés après le premier redémarrage. La raison se trouve dans sa définition. on-failure réagit à une seule situation : l’arrêt du processus du conteneur avec un code d’erreur.
Un redémarrage n’est pas une erreur. Lorsque l’hôte s’arrête, systemd arrête docker.service, puis le daemon arrête volontairement chaque conteneur. Le conteneur n’a pas échoué ; la policy n’a donc rien à traiter. Au redémarrage, le daemon examine les conteneurs qu’il doit relancer. Un conteneur on-failure arrêté proprement n’en fait pas partie. Il reste dans l’état exited.
Vous pouvez le vérifier directement. Définissez restart: on-failure sur un service, exécutez docker compose up -d, redémarrez, puis exécutez :
docker compose ps -aLe service apparaît avec l’état Exited et un statut tel que Exited (0) 2 minutes ago. Rien n’est défaillant et aucune erreur n’est enregistrée, ce qui rend le problème difficile à diagnostiquer. La policy a fait exactement ce qui est prévu.
on-failure reste utile. Cette option convient à un conteneur qui exécute une tâche et peut planter, lorsque vous voulez limiter le nombre de tentatives et éviter une boucle de redémarrage. Elle ne convient pas pour maintenir un service longue durée actif après les redémarrages.
Les politiques de redémarrage ne fonctionnent que si le service Docker démarre au boot
Les politiques de redémarrage sont appliquées par le daemon Docker. Si le daemon ne démarre pas, rien ne les applique. Vérifiez son état :
systemctl is-enabled docker
systemctl is-enabled containerdLes deux commandes doivent afficher enabled. Les paquets du dépôt officiel de Docker les activent lors de l’installation. Sur un serveur neuf, ce contrôle réussit donc généralement. Si l’une des commandes affiche disabled, corrigez la configuration :
sudo systemctl enable --now docker containerdUn piège mérite d’être expliqué. Ubuntu fournit également docker.socket, qui démarre le daemon à la demande dès qu’un processus communique avec l’API Docker. Certains voient docker.socket activé, supposent que le daemon est ainsi couvert et désactivent docker.service pour économiser de la mémoire. Au boot, aucun processus n’appelle l’API. Le socket n’est donc jamais sollicité, le daemon ne démarre pas et aucun conteneur ne démarre avant l’exécution de votre première commande docker. L’activation par socket ne remplace pas l’activation de docker.service.
Quand une unité systemd est préférable
Les politiques de redémarrage ne gèrent pas l’ordre de démarrage par rapport au reste du système. Le daemon démarre et lance vos conteneurs dès que possible. Si votre stack monte un répertoire depuis un volume distinct, un partage NFS (network file system) ou un disque chiffré, les conteneurs peuvent démarrer avant que ce chemin existe. Docker crée alors sans problème un répertoire vide au point de montage et démarre le conteneur avec celui-ci. Votre base de données démarre donc sans données.
Écrivez une unité systemd dans les cas suivants. La stack dépend d’un montage, d’une interface VPN ou d’une autre unité qui doit être prête auparavant. Vous voulez que systemctl stop myapp et systemctl start myapp fonctionnent comme pour tous les autres services du serveur. Vous voulez aussi que la stack soit arrêtée proprement lors de l’extinction, au lieu d’être tuée en même temps que le daemon. Si les unités systemd sont nouvelles pour vous, écrire un service et un timer systemd présente le format du fichier plus en détail.
Rédiger l’unité systemd
Placez la stack dans un chemin fixe, en dehors d’un répertoire personnel. /srv/myapp convient bien, car une unité qui s’exécute avant toute connexion utilisateur n’a aucune raison de lire /home.
Créez /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.targetActivez et démarrez-la :
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
systemctl status myapp.serviceUne unité fonctionnelle affiche Active: active (exited). Cela semble incorrect la première fois. C’est pourtant attendu : Type=oneshot avec RemainAfterExit=yes signifie que l’unité a exécuté sa commande, que cette commande s’est terminée et que systemd maintient l’unité à l’état active afin que ExecStop s’exécute à l’arrêt.
Chaque ligne a son utilité. Requires=docker.service signifie que l’unité échoue immédiatement au lieu d’exécuter docker compose sur un socket indisponible. After= définit l’ordre de démarrage, car Requires= ne le fait pas à lui seul. RequiresMountsFor= demande à systemd d’inclure l’unité de montage correspondant à ce chemin et d’attendre qu’elle soit disponible. C’est précisément l’intérêt d’une unité par rapport à une restart policy. TimeoutStartSec=0 empêche systemd d’arrêter le job de démarrage pendant le téléchargement d’une image volumineuse.
À propos de la combinaison de ces deux mécanismes. La documentation de Docker déconseille d’associer les restart policies à un process manager de l’hôte. Cet avertissement concerne un process manager qui supervise directement le processus du conteneur et le redémarre pendant que le daemon tente de faire la même chose. Une unité Type=oneshot ne supervise rien. Vous pouvez donc conserver restart: unless-stopped dans le fichier compose avec cette unité. C’est même ce qu’il faut faire. systemd gère l’ordre au démarrage, et le daemon gère un conteneur qui s’arrête à trois heures du matin.
L’unité est différente lorsque l’élément à maintenir en fonctionnement est un processus long-running simple plutôt qu’une stack. Dans ce cas, aucun daemon ne se trouve en dessous et le propre Restart= de systemd doit assurer la supervision. exécuter dsh sans interface derrière systemd présente un exemple complet de cette configuration, avec l’utilisateur dédié et le journal.
Vérifier avec un vrai redémarrage
Rien ne remplace un test réel. systemctl restart docker ne vérifie pas l’ordre de montage, et docker compose down suivi de docker compose up -d ne vérifie rien concernant le démarrage.
sudo rebootAttendez, reconnectez-vous, puis vérifiez dans cet ordre :
uptime
systemctl is-active docker
docker compose psuptime confirme que vous êtes bien devant une machine qui a réellement redémarré. Depuis le répertoire de la stack, docker compose ps doit afficher chaque service avec l’état running et un uptime proche de celui de la machine. Un service affichant Exited est celui qu’il faut examiner.
Si un service n’a pas démarré, le log du daemon couvre la période du boot :
journalctl -u docker.service -b --no-pager | tail -50Pour une stack gérée par une unit, journalctl -u myapp.service -b --no-pager affiche la sortie exacte de docker compose depuis le boot, notamment en cas d’échec du pull d’une image ou de fichier .env manquant. Le redémarrage que vous planifiez est celui que vous surveillez. Demandez donc à l’unit de vous signaler les suivants : une ligne OnFailure= pointant vers un serveur ntfy auto-hébergé transforme une stack qui n’a pas redémarré en notification push, au lieu de vous laisser la découvrir plusieurs jours plus tard.
Éléments qui interrompent discrètement le démarrage automatique
Les conteneurs créés avec docker compose run n’héritent jamais de la restart policy définie dans le fichier. Compose les traite comme des conteneurs ponctuels. Si un service semble ignorer sa policy, vérifiez s’il a été démarré avec run au lieu de up.
Un chemin relatif dans un volume ou dans une entrée env_file est résolu par rapport au répertoire du fichier Compose. Cela fonctionne depuis votre shell et depuis une unité qui définit WorkingDirectory. Cela échoue depuis une unité qui ne le définit pas, car le répertoire de travail est alors /.
Docker rootless est un cas distinct. Le daemon s’exécute comme un service utilisateur, et un service utilisateur s’arrête lorsque la dernière session de cet utilisateur se termine. Activez-le pour l’utilisateur et autorisez-le à continuer de fonctionner même lorsqu’aucun utilisateur n’est connecté :
systemctl --user enable docker
sudo loginctl enable-linger $USERSans enable-linger, le daemon rootless s’arrête lorsque vous vous déconnectez, et les conteneurs s’arrêtent avec lui. Cela ressemble exactement à une restart policy défaillante.
Dernier point. Les mises à jour de sécurité automatiques peuvent redémarrer un serveur à une heure fixe. C’est utile uniquement si votre stack redémarre automatiquement. La configuration de ce comportement sur une nouvelle machine fait partie des tâches à effectuer pendant la première heure, avec les dix premières minutes sur un nouveau VPS.
FAQ
Quelle est la différence entre restart: always et restart: unless-stopped ?
Les deux redémarrent le conteneur lorsqu’il s’arrête de lui-même. La différence apparaît lorsque vous arrêtez un conteneur manuellement. Avec always, le conteneur redémarre au prochain démarrage du daemon Docker. Un redémarrage du système annule donc votre arrêt manuel. Avec unless-stopped, le daemon mémorise que le conteneur a été arrêté volontairement et le laisse arrêté. Utilisez unless-stopped, sauf si vous voulez précisément qu’un conteneur reste arrêté.
J’ai ajouté restart: unless-stopped, mais le conteneur ne démarre toujours pas après un redémarrage. Pourquoi ?
La policy est enregistrée sur le conteneur, pas dans le fichier. La modification du fichier YAML ne met pas à jour un conteneur existant. Exécutez docker compose up -d pour que Compose le recrée, puis vérifiez avec docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app). Si la commande affiche no, le conteneur a été créé avant votre modification. L’autre cause courante est que docker.service n’est pas activé. Vous pouvez le vérifier avec systemctl is-enabled docker.
Ai-je besoin d’une unité systemd si j’utilise déjà des restart policies ?
En général, non. Une restart policy suffit pour une stack qui dépend uniquement du réseau, ce qui est le cas de la plupart des stacks. Ajoutez une unité lorsque les conteneurs dépendent d’une ressource qui n’est pas prête au démarrage du daemon Docker, par exemple un disque externe, un volume chiffré, un partage NFS ou une interface VPN. L’unité permet de définir l’ordre de démarrage avec After= et RequiresMountsFor=, ce qu’une restart policy ne permet pas d’exprimer.
Comment arrêter définitivement une stack sans qu’elle redémarre au prochain reboot ?
Avec unless-stopped, docker compose stop suffit, car un conteneur arrêté manuellement ne redémarre pas lorsque le daemon est relancé. Avec always, un simple arrêt ne suffit pas et le conteneur redémarre après un reboot. Exécutez docker compose down pour supprimer les conteneurs, ou modifiez d’abord la policy avec docker update --restart no my-container. Si une unité systemd gère la stack, exécutez également sudo systemctl disable myapp.service. Sinon, l’unité la redémarrera.