Pourquoi votre conteneur Docker redémarre en boucle (et comment le corriger)
Un conteneur Docker qui redémarre en boucle (souvent visible via un statut Restarting (1) ...) est presque toujours le symptôme d’un processus principal qui se termine (crash, exit volontaire, signal, mauvaise config), combiné à une politique de redémarrage (restart: always, unless-stopped, etc.) ou à un orchestrateur (Docker Compose, Swarm, Kubernetes) qui tente de le relancer.
Ce tutoriel explique en profondeur les causes les plus fréquentes, comment diagnostiquer correctement (avec de vraies commandes), et comment corriger durablement.
1) Comprendre ce qui se passe réellement
1.1 Le principe fondamental : un conteneur vit tant que son PID 1 vit
Dans un conteneur, le processus lancé par CMD/ENTRYPOINT devient généralement le PID 1. Si ce processus se termine, le conteneur s’arrête. Exemple typique :
- Vous lancez
nginx -g 'daemon off;'→ nginx reste au premier plan → conteneur reste “Up”. - Vous lancez
nginx(sansdaemon off) → nginx se met en arrière-plan → le PID 1 se termine → conteneur s’arrête.
Si une politique de redémarrage est active, Docker relance le conteneur, et vous voyez une boucle.
1.2 La politique de redémarrage (restart policy)
Les politiques courantes :
no(par défaut) : pas de redémarrage automatique.on-failure[:max-retries]: redémarre si le code de sortie est non nul.always: redémarre toujours, même si le code de sortie est 0.unless-stopped: commealways, sauf si vous l’arrêtez explicitement.
Voir la politique :
docker inspect -f '{{.HostConfig.RestartPolicy.Name}} {{.HostConfig.RestartPolicy.MaximumRetryCount}}' <container>
Avec Docker Compose, on la voit dans docker compose config ou dans le fichier compose.yml.
1.3 Le “backoff” : pourquoi les redémarrages ralentissent
Docker applique un délai croissant entre redémarrages (backoff) pour éviter de marteler la machine. Ce n’est pas une “solution”, juste une protection.
2) Reconnaître le problème : symptômes et signaux
2.1 Statuts et codes de sortie
Lister les conteneurs :
docker ps -a
Vous verrez souvent :
Restarting (1) ...: le processus sort avec code 1 et redémarre.Exited (0) ...: le processus s’est terminé “normalement” (mais sirestart: always, il redémarrera quand même).Exited (137): tué par SIGKILL, souvent OOM (Out Of Memory) oudocker kill.Exited (143): SIGTERM (arrêt demandé).Exited (126)/Exited (127): problème d’exécution (permission / commande introuvable).
2.2 Les événements Docker (très utile)
Suivre les événements en direct :
docker events --filter container=<container>
Vous verrez des séquences die puis start répétées, avec parfois des informations sur le code de sortie.
3) Diagnostic rapide en 5 minutes (checklist)
Étape A — Voir les logs (même si ça redémarre)
docker logs --tail=200 <container>
En mode “follow” :
docker logs -f <container>
Si le conteneur redémarre trop vite, les logs peuvent défiler. Ajoutez un --since :
docker logs --since=10m <container>
Étape B — Inspecter l’état et le code de sortie
docker inspect <container> --format \
'Status={{.State.Status}} ExitCode={{.State.ExitCode}} OOMKilled={{.State.OOMKilled}} Error={{.State.Error}} FinishedAt={{.State.FinishedAt}}'
Étape C — Vérifier la commande réellement exécutée
docker inspect <container> --format \
'Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}}'
Étape D — Vérifier la politique de redémarrage
docker inspect <container> --format \
'RestartPolicy={{json .HostConfig.RestartPolicy}}'
Étape E — Tenter un shell “dans” l’image (sans utiliser le conteneur qui crash)
Si le conteneur redémarre en boucle, vous n’avez pas toujours le temps de faire docker exec. Lancez plutôt un conteneur interactif à partir de la même image :
docker run --rm -it --entrypoint sh <image>:<tag>
Ou bash si disponible :
docker run --rm -it --entrypoint bash <image>:<tag>
Puis testez la commande de démarrage à la main.
4) Causes fréquentes (et corrections concrètes)
4.1 Le processus principal se termine immédiatement (conteneur “trop court”)
Symptôme
- Logs vides ou “done”.
ExitCode=0ouExitCode=1.- Exemple : image qui lance un script qui finit.
Exemple typique
Vous avez un CMD ["python", "script.py"] et script.py se termine.
Correction
- Si vous voulez un service long, démarrez un serveur (ou un worker) qui reste au premier plan.
- Si vous voulez exécuter une tâche ponctuelle (job), ne mettez pas de restart policy “always”.
Avec Docker Compose, remplacez :
docker compose up -d
par un lancement “one-shot” :
docker compose run --rm <service> <commande>
Ou changez la politique :
restart: "no"(ou retirezrestart).
Astuce : garder le conteneur vivant pour debug
Temporairement :
docker run --rm -it --entrypoint sh <image>
# puis:
tail -f /dev/null
Cela n’est pas une solution de prod, mais utile pour diagnostiquer.
4.2 Mauvaise commande / commande introuvable (exit 127)
Symptôme
ExitCode=127- Logs :
exec: "xxx": executable file not found in $PATH
Diagnostic
Vérifiez PATH et la présence du binaire :
docker run --rm -it --entrypoint sh <image> -lc 'echo $PATH; which xxx; ls -l /usr/local/bin'
Corrections
- Installer le paquet manquant dans l’image.
- Corriger
CMD/ENTRYPOINT. - Si vous utilisez un script, vérifiez le shebang (
#!/bin/sh,#!/usr/bin/env bash, etc.) et les fins de ligne Windows (CRLF).
Convertir CRLF → LF :
sed -i 's/\r$//' entrypoint.sh
Et rendre exécutable :
chmod +x entrypoint.sh
4.3 Problème de permissions (exit 126)
Symptôme
ExitCode=126- Logs :
permission denied
Diagnostic
Dans un shell de l’image :
ls -l /chemin/vers/script.sh
id
Si vous exécutez en utilisateur non-root (bonne pratique), assurez-vous que le fichier est exécutable et accessible.
Corrections
- Ajouter
chmod +xlors du build. - Ajuster propriétaire et droits :
chown -R app:app /app
chmod -R u+rwX,go+rX /app
- Éviter de monter un volume avec des permissions incompatibles (voir section volumes).
4.4 Application qui crash au démarrage (mauvaise config, variable manquante, migration DB, etc.)
Symptôme
- Logs explicites : “missing env”, “cannot connect”, “invalid configuration”.
ExitCode=1ou autre.
Diagnostic
Lire les logs, puis vérifier l’environnement :
docker inspect <container> --format '{{json .Config.Env}}' | tr ',' '\n'
Si vous utilisez Compose :
docker compose config
Vérifier les variables réellement injectées, les fichiers .env, les substitutions, etc.
Corrections
- Définir les variables manquantes.
- Valider la config au démarrage (fail-fast) mais fournir des messages clairs.
- Ajouter une commande de “check” dans l’image (si possible).
Exemple : tester la connectivité DB depuis un conteneur de debug :
docker run --rm -it --network <votre_network> postgres:16-alpine \
sh -lc 'pg_isready -h db -p 5432 -U postgres'
4.5 Dépendance non prête (race condition) : DB/queue/API pas encore disponible
Beaucoup confondent depends_on (ordre de lancement) avec “service prêt”. Docker Compose ne garantit pas que la DB est prête à accepter des connexions.
Symptôme
- L’app démarre, tente de se connecter, échoue, quitte → redémarre → boucle.
- Logs : “connection refused”, “timeout”, etc.
Corrections (approches)
- Retry/backoff dans l’application (recommandé).
- Script d’attente (wait-for-it, dockerize, etc.).
- Healthchecks et logique de dépendance (selon orchestrateur).
Exemple de test en boucle côté shell (diagnostic) :
docker exec -it <container> sh -lc 'for i in $(seq 1 30); do nc -zv db 5432 && exit 0; echo "wait..."; sleep 1; done; exit 1'
Si votre image n’a pas nc, utilisez apk add (Alpine) ou apt-get (Debian/Ubuntu) dans une image de debug, ou un conteneur utilitaire sur le même réseau.
4.6 OOMKilled (exit 137) : manque de mémoire
Symptôme
OOMKilled=truedansdocker inspectExitCode=137- Souvent pas de logs (process tué brutalement).
Diagnostic
Vérifier :
docker inspect <container> --format 'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}}'
Voir la conso mémoire (si le conteneur tient assez longtemps) :
docker stats <container>
Sur Linux, vous pouvez aussi regarder les logs kernel (selon système) :
dmesg | tail -n 50
Corrections
- Augmenter la mémoire allouée (Docker Desktop) ou les limites.
- Réduire l’empreinte : paramètres JVM (
-Xmx), Node (--max-old-space-size), workers, cache, etc. - Éviter de charger de gros fichiers en mémoire au démarrage.
Exemple JVM :
JAVA_TOOL_OPTIONS="-Xms256m -Xmx512m"
4.7 Mauvais signal handling / PID 1 (zombies, arrêt brutal, redémarrages inattendus)
Le PID 1 a des responsabilités particulières (récolte des processus zombies, gestion des signaux). Certains programmes ne gèrent pas bien SIGTERM quand ils sont PID 1, ou vous lancez un shell qui ne relaie pas les signaux.
Symptôme
- Arrêts bizarres, redémarrages après
docker stop, ou comportements incohérents. - En Compose/Swarm, l’orchestrateur peut tuer/restart si le service ne s’arrête pas proprement.
Corrections
- Utiliser
execdans les scripts d’entrypoint pour remplacer le shell par le processus. - Utiliser un init minimal comme
tini.
Exemple Dockerfile (Debian/Ubuntu) :
apt-get update && apt-get install -y tini
Puis :
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["votre-processus", "args"]
Dans un entrypoint.sh, terminez par :
exec votre-processus "$@"
4.8 Problèmes de volumes : fichiers manquants, permissions, chemins masqués
Monter un volume peut masquer le contenu construit dans l’image. Exemple : vous avez /app dans l’image avec node_modules, puis vous montez ./app:/app depuis l’hôte : les node_modules de l’image disparaissent.
Symptôme
- Au runtime : “module not found”, “file not found”, etc.
- Boucle de redémarrage car l’app ne démarre pas.
Diagnostic
Comparer le système de fichiers avec et sans volume :
docker run --rm -it <image> sh -lc 'ls -la /app | head'
Puis dans le conteneur réel (si possible) :
docker exec -it <container> sh -lc 'mount | head; ls -la /app | head'
Corrections
- Monter des volumes plus ciblés (ex :
/app/dataau lieu de/app). - Utiliser des volumes nommés pour
node_modules:
docker volume create app_node_modules
docker run -v app_node_modules:/app/node_modules ...
- Ajuster permissions (surtout sur Linux) : UID/GID,
chown, etc.
4.9 Healthcheck qui échoue et orchestrateur qui redémarre (ou vous qui le faites)
Docker “pur” n’arrête pas automatiquement un conteneur si son HEALTHCHECK est unhealthy, mais certains setups (scripts, orchestrateurs, systèmes de supervision) redémarrent les conteneurs unhealthy.
Diagnostic
Voir l’état health :
docker inspect <container> --format '{{json .State.Health}}'
Voir les logs de healthcheck :
docker inspect <container> --format '{{range .State.Health.Log}}{{println .End .ExitCode .Output}}{{end}}'
Corrections
- Corriger la commande de healthcheck.
- Augmenter
interval,timeout,retries,start_period. - Faire un endpoint
/healthfiable (pas dépendant de services externes si possible).
4.10 Mauvaise architecture/plateforme (exec format error)
Si vous exécutez une image ARM sur x86_64 (ou inversement), vous pouvez obtenir exec format error, et le conteneur redémarre.
Diagnostic
Voir l’architecture :
docker image inspect <image> --format '{{.Os}}/{{.Architecture}}'
uname -m
Correction
- Utiliser une image multi-arch.
- Forcer la plateforme (Docker Desktop) :
docker run --platform linux/amd64 <image>
Ou construire multi-arch :
docker buildx build --platform linux/amd64,linux/arm64 -t vous/image:tag --push .
5) Méthode de debug “propre” quand ça redémarre trop vite
5.1 Désactiver temporairement le restart
Pour un conteneur existant :
docker update --restart=no <container>
docker stop <container>
docker start <container>
Ensuite, il s’arrêtera au crash sans repartir, ce qui facilite l’inspection.
Avec Compose, vous pouvez commenter restart: puis :
docker compose up -d --force-recreate
5.2 Remplacer l’entrypoint pour entrer dans le conteneur
Si vous voulez lancer le même conteneur mais avec un shell :
docker run --rm -it --entrypoint sh --network <network> \
-e VOS_VARS=... \
-v vos_volumes... \
<image>:<tag>
Puis exécutez la commande originale à la main (copiée depuis docker inspect).
5.3 Capturer la configuration complète
Utile pour partager à un collègue :
docker inspect <container> > inspect.json
docker logs <container> > logs.txt
6) Cas pratiques (avec commandes réelles)
6.1 Nginx qui quitte immédiatement
Problème
Vous lancez nginx sans le garder au premier plan.
Diagnostic
Logs :
docker logs <nginx_container>
Souvent vide.
Correction
Démarrer nginx en foreground :
docker run -d --name web --restart=always nginx:alpine nginx -g 'daemon off;'
Ou dans une image custom, CMD ["nginx", "-g", "daemon off;"].
6.2 Node.js : “Cannot find module” à cause d’un volume
Problème
Vous montez votre code sur /app et écrasez node_modules installé dans l’image.
Diagnostic
Dans l’image :
docker run --rm -it <image> sh -lc 'ls -la /app/node_modules | head'
Dans le conteneur avec volume, node_modules n’existe plus.
Correction
Monter uniquement le code, et garder node_modules dans un volume :
docker volume create myapp_node_modules
docker run -d --name myapp --restart=always \
-v "$PWD":/app \
-v myapp_node_modules:/app/node_modules \
-w /app \
node:20-alpine sh -lc "npm ci && npm start"
En production, préférez construire une image qui contient déjà les dépendances, et éviter les montages de code.
6.3 Application qui redémarre car DB pas prête
Diagnostic rapide
Depuis un conteneur utilitaire sur le même réseau :
docker run --rm -it --network <network> alpine:3.20 sh -lc \
'apk add --no-cache netcat-openbsd; for i in $(seq 1 30); do nc -z db 5432 && echo OK && exit 0; echo wait; sleep 1; done; exit 1'
Si ça échoue, la DB n’est pas accessible (DNS, réseau, port, credentials, firewall).
Correction
- Corriger le réseau (même network Docker).
- Ajouter retry dans l’app.
- Vérifier host/port : dans Docker,
localhostdans le conteneur = le conteneur lui-même, pas la DB.
7) Bonnes pratiques pour éviter les boucles de redémarrage
7.1 Logs clairs et “fail-fast” utile
Si l’app doit quitter, qu’elle affiche pourquoi (config manquante, migration, permission). Un crash silencieux rend le diagnostic pénible.
7.2 Ne pas utiliser restart: always par réflexe
- Pour un service critique :
unless-stoppedpeut être correct. - Pour un job : pas de restart, ou
on-failure:3avec une limite.
7.3 Healthchecks pertinents
Un healthcheck doit vérifier l’essentiel (process vivant + dépendances minimales), sans être trop strict.
7.4 Gérer correctement PID 1 et les signaux
Utilisez exec, évitez les shells inutiles, considérez tini.
7.5 Tester la commande de démarrage localement
Avant d’ajouter restart: always, lancez :
docker run --rm <image> <commande>
echo $?
Si le code de sortie n’est pas celui attendu, corrigez avant de déployer.
8) Guide de dépannage : table “symptôme → cause → action”
| Symptôme | Cause probable | Action |
|---|---|---|
Restarting (1) + logs “missing env” | Variable d’environnement manquante | Vérifier docker inspect ... Env, .env, secrets |
Exited (127) | Commande introuvable | Corriger CMD/ENTRYPOINT, installer le binaire |
Exited (126) | Permissions | chmod +x, chown, vérifier utilisateur |
Exited (137) + OOMKilled=true | Out of memory | Augmenter mémoire, réduire usage, régler JVM/Node |
| Logs “connection refused” | Dépendance pas prête / mauvais host | Retry, healthchecks, utiliser le bon nom de service |
| Rien dans les logs, exit 0 | Process finit immédiatement | Lancer un process long, retirer restart policy |
exec format error | Mauvaise architecture | Image multi-arch, --platform |
9) Procédure complète recommandée (pas à pas)
-
Identifier le conteneur qui boucle :
docker ps -a -
Lire les logs :
docker logs --tail=200 <container> -
Inspecter l’état :
docker inspect <container> --format \ 'ExitCode={{.State.ExitCode}} OOMKilled={{.State.OOMKilled}} Error={{.State.Error}}' -
Vérifier la commande :
docker inspect <container> --format 'Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}}' -
Désactiver temporairement le restart pour stabiliser le diagnostic :
docker update --restart=no <container> docker stop <container> docker start <container> -
Reproduire dans un conteneur interactif :
docker run --rm -it --entrypoint sh <image>:<tag>Puis lancer la commande de démarrage et observer l’erreur.
-
Corriger (config, dépendances, permissions, mémoire, volumes, etc.), reconstruire l’image si nécessaire, puis remettre une politique de redémarrage adaptée.
10) Conclusion
Un conteneur Docker qui redémarre en boucle n’est pas “un bug Docker” : c’est un processus qui se termine, et un système qui le relance. La clé est de déterminer pourquoi le PID 1 quitte :
- erreur de commande (
127), permission (126), - crash applicatif (config, dépendances, migrations),
- OOM (
137), - volumes qui masquent des fichiers,
- architecture incompatible,
- ou simplement un service lancé en arrière-plan.
Avec les commandes de ce tutoriel (docker logs, docker inspect, docker events, docker run --entrypoint sh, désactivation temporaire du restart), vous pouvez passer d’un “ça boucle” à une cause précise, puis appliquer une correction robuste.
Si vous partagez la sortie de docker ps -a, docker inspect (les champs State, Config.Cmd/Entrypoint, HostConfig.RestartPolicy) et les 200 dernières lignes de docker logs, on peut généralement identifier la cause en quelques minutes.