← Retour aux tutoriels

Pourquoi votre conteneur Docker redémarre en boucle (et comment le corriger)

dockerdevopscontainerstroubleshootingrestart-policylogshealthcheckoomkill

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 :

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 :

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 :

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

Exemple typique

Vous avez un CMD ["python", "script.py"] et script.py se termine.

Correction

Avec Docker Compose, remplacez :

docker compose up -d

par un lancement “one-shot” :

docker compose run --rm <service> <commande>

Ou changez la politique :

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

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

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

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

chown -R app:app /app
chmod -R u+rwX,go+rX /app

4.4 Application qui crash au démarrage (mauvaise config, variable manquante, migration DB, etc.)

Symptôme

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

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

Corrections (approches)

  1. Retry/backoff dans l’application (recommandé).
  2. Script d’attente (wait-for-it, dockerize, etc.).
  3. 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

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

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

Corrections

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

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

docker volume create app_node_modules
docker run -v app_node_modules:/app/node_modules ...

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


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

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


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

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ômeCause probableAction
Restarting (1) + logs “missing env”Variable d’environnement manquanteVérifier docker inspect ... Env, .env, secrets
Exited (127)Commande introuvableCorriger CMD/ENTRYPOINT, installer le binaire
Exited (126)Permissionschmod +x, chown, vérifier utilisateur
Exited (137) + OOMKilled=trueOut of memoryAugmenter mémoire, réduire usage, régler JVM/Node
Logs “connection refused”Dépendance pas prête / mauvais hostRetry, healthchecks, utiliser le bon nom de service
Rien dans les logs, exit 0Process finit immédiatementLancer un process long, retirer restart policy
exec format errorMauvaise architectureImage multi-arch, --platform

9) Procédure complète recommandée (pas à pas)

  1. Identifier le conteneur qui boucle :

    docker ps -a
  2. Lire les logs :

    docker logs --tail=200 <container>
  3. Inspecter l’état :

    docker inspect <container> --format \
    'ExitCode={{.State.ExitCode}} OOMKilled={{.State.OOMKilled}} Error={{.State.Error}}'
  4. Vérifier la commande :

    docker inspect <container> --format 'Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}}'
  5. Désactiver temporairement le restart pour stabiliser le diagnostic :

    docker update --restart=no <container>
    docker stop <container>
    docker start <container>
  6. Reproduire dans un conteneur interactif :

    docker run --rm -it --entrypoint sh <image>:<tag>

    Puis lancer la commande de démarrage et observer l’erreur.

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

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.