Votre conteneur affiche “Exited (137)” et vous ne savez pas pourquoi ? Ce guide vous donne les réflexes de diagnostic pour comprendre ce qui se passe dans un conteneur : cycle de vie, exit codes, logs, inspect et events. En 10 minutes, vous saurez où chercher quand quelque chose ne fonctionne pas.
Checklist express (60 secondes)
Section intitulée « Checklist express (60 secondes) »Vous êtes pressé ? Voici les 5 commandes à lancer immédiatement :
# 1. État et exit codedocker ps -a --filter name=mon_conteneur
# 2. Logs récents (même si le conteneur est arrêté)docker logs --tail 100 mon_conteneur
# 3. Infos clés en une commandedocker inspect --format 'Exit={{.State.ExitCode}} OOM={{.State.OOMKilled}} Restarts={{.RestartCount}} Error={{.State.Error}}' mon_conteneur
# 4. Événements récents (die, oom, kill, health)docker events --since 10m --filter container=mon_conteneur
# 5. Ressources (si le conteneur tourne)docker stats --no-stream mon_conteneurLa suite du guide détaille chaque commande et explique comment interpréter les résultats.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Cycle de vie : les états d’un conteneur (created → running → exited)
- Exit codes : décoder 0, 1, 137, 143 et comprendre la cause
- HEALTHCHECK : surveiller la santé de vos conteneurs automatiquement
- Restart policies : redémarrer automatiquement après un crash
- Outils de diagnostic : logs, inspect, events, stats et docker debug
- Stop vs Kill : quelle différence et quand utiliser l’un ou l’autre
- Debug réseau : premiers réflexes quand un conteneur ne communique pas
Le cycle de vie d’un conteneur
Section intitulée « Le cycle de vie d’un conteneur »Un conteneur passe par plusieurs états au cours de sa vie. Comprendre ces états est la première étape du diagnostic.
| État | Description | Commande qui y mène |
|---|---|---|
| created | Conteneur créé mais pas démarré | docker create |
| running | En cours d’exécution | docker start / docker run |
| paused | Processus gelés (mais en mémoire) | docker pause |
| exited | Processus terminé (code de sortie disponible) | docker stop / crash |
| restarting | En cours de redémarrage | Politique de restart |
| removing | En cours de suppression | docker rm |
# Voir l'état actuel d'un conteneurdocker ps -a --filter "name=mon_conteneur"
# Résultat typiqueCONTAINER ID IMAGE STATUS NAMESabc123 nginx Exited (137) 2 hours ago mon_conteneurDécoder les exit codes
Section intitulée « Décoder les exit codes »Quand un conteneur s’arrête, il retourne un exit code (code de sortie). Ce code indique la raison de l’arrêt. Bonne nouvelle : même si le conteneur ne tourne plus, Docker conserve cette information tant que vous n’avez pas supprimé le conteneur avec docker rm.
Récupérer l’exit code d’un conteneur arrêté
Section intitulée « Récupérer l’exit code d’un conteneur arrêté »Méthode 1 : via docker ps -a — L’exit code apparaît directement dans la colonne STATUS :
docker ps -a --filter "name=mon_app"# CONTAINER ID IMAGE STATUS NAMES# abc123 nginx Exited (137) 2 hours ago mon_app# ^^^# Exit code ici !Méthode 2 : via docker inspect — Pour récupérer uniquement le code, sans le reste :
# Récupérer l'exit code seuldocker inspect --format='{{.State.ExitCode}}' mon_app# Résultat : 137
# Récupérer plusieurs infos d'un coupdocker inspect --format='Exit: {{.State.ExitCode}}, OOM: {{.State.OOMKilled}}' mon_app# Résultat : Exit: 137, OOM: trueMéthode 3 : filtrer par exit code — Utile pour trouver tous les conteneurs qui ont crashé :
# Lister tous les conteneurs arrêtés avec exit code 137 (OOM/kill)docker ps -a --filter "exited=137"
# Lister tous les conteneurs en erreur (exit != 0)docker ps -a --filter "status=exited" --filter "exited=1"
# Format personnalisé pour un tableau lisibledocker ps -a --filter "status=exited" --format "table {{.Names}}\t{{.Status}}\t{{.Image}}"Tableau des exit codes courants
Section intitulée « Tableau des exit codes courants »| Exit code | Signification | Cause probable |
|---|---|---|
| 0 | Succès | Le processus s’est terminé normalement |
| 1 | Erreur générique | Bug dans l’application, mauvaise config |
| 126 | Permission denied | Le binaire n’est pas exécutable |
| 127 | Command not found | La commande n’existe pas dans l’image |
| 137 | SIGKILL (128+9) | OOM kill ou docker kill |
| 143 | SIGTERM (128+15) | Arrêt propre via docker stop |
| 139 | SIGSEGV (128+11) | Segmentation fault (crash mémoire) |
Diagnostic rapide par exit code :
- Exit 0 → Tout va bien, le conteneur a fini son travail
- Exit 1 → Regardez les logs (
docker logs) - Exit 137 → Vérifiez les limites mémoire ou si quelqu’un a fait
docker kill - Exit 143 → Arrêt normal via
docker stop - Exit 127 → Vérifiez que la commande existe dans l’image
Arbre de décision : quel exit code ?
Section intitulée « Arbre de décision : quel exit code ? »Utilisez ce schéma pour diagnostiquer rapidement la cause d’un arrêt :
Surveiller la santé avec HEALTHCHECK
Section intitulée « Surveiller la santé avec HEALTHCHECK »Un conteneur peut être en état running mais ne plus répondre correctement — l’application a planté, une dépendance est tombée, ou la base de données est inaccessible. C’est là qu’intervient le HEALTHCHECK : une sonde qui vérifie périodiquement si votre application fonctionne vraiment.
Les trois états de santé
Section intitulée « Les trois états de santé »Un conteneur avec HEALTHCHECK peut être dans l’un de ces états :
- starting : Le conteneur vient de démarrer, on lui laisse le temps de s’initialiser (période de grâce)
- healthy : Le dernier check a réussi, tout fonctionne
- unhealthy : Plusieurs checks consécutifs ont échoué, il y a un problème
Configurer un HEALTHCHECK dans le Dockerfile
Section intitulée « Configurer un HEALTHCHECK dans le Dockerfile »La syntaxe comporte quatre paramètres importants :
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ CMD curl -f http://localhost:8080/health || exit 1Décortiquons chaque paramètre :
--interval=30s: Vérifie toutes les 30 secondes. Trop fréquent = surcharge, trop rare = détection lente.--timeout=3s: Si le check ne répond pas en 3 secondes, c’est un échec. Adaptez selon la charge de votre app.--start-period=10s: Pendant les 10 premières secondes, les échecs ne comptent pas. Utile pour les apps lentes à démarrer.--retries=3: Il faut 3 échecs consécutifs avant de passerunhealthy. Évite les faux positifs sur un pic de charge.
Diagnostiquer l’état de santé
Section intitulée « Diagnostiquer l’état de santé »Une fois le HEALTHCHECK configuré, vous pouvez surveiller l’état de votre conteneur :
# Voir l'état de santé actueldocker inspect --format='{{.State.Health.Status}}' mon_conteneur# Résultat : starting, healthy ou unhealthy
# Voir l'historique des checks (les 5 derniers)docker inspect --format='{{json .State.Health}}' mon_conteneur | jq
# Filtrer les conteneurs unhealthydocker ps --filter health=unhealthyL’historique des checks vous montre les sorties de chaque vérification, ce qui est précieux pour comprendre pourquoi un conteneur est passé unhealthy.
Restart policies : redémarrer automatiquement
Section intitulée « Restart policies : redémarrer automatiquement »Quand un conteneur crash, vous ne voulez pas forcément le redémarrer manuellement à 3h du matin. Les restart policies permettent à Docker de gérer automatiquement les redémarrages.
Les quatre policies
Section intitulée « Les quatre policies »no (défaut) : Docker ne redémarre jamais le conteneur. C’est à vous de le faire manuellement. Utile en développement quand vous voulez voir les crashes.
always : Docker redémarre le conteneur quoi qu’il arrive — même après un docker stop, même après un reboot du serveur. Attention : si votre conteneur a un bug qui le fait crasher immédiatement, il va redémarrer en boucle infinie.
unless-stopped : Comme always, mais si vous faites docker stop, le conteneur reste arrêté même après un reboot du daemon Docker. C’est souvent le meilleur choix en production.
on-failure[:max] : Redémarre uniquement si le conteneur s’arrête avec un exit code non-zéro (donc pas après docker stop qui donne exit 143). Vous pouvez limiter le nombre de tentatives avec :max.
Choisir la bonne policy
Section intitulée « Choisir la bonne policy »# Développement : pas de restart (voir les crashes)docker run -d --restart no mon_app
# Production : restart sauf arrêt manueldocker run -d --restart unless-stopped mon_app
# Service critique : toujours restartdocker run -d --restart always mon_app
# Limité à 5 tentatives sur erreurdocker run -d --restart on-failure:5 mon_appPour modifier la policy d’un conteneur existant sans le recréer :
docker update --restart unless-stopped mon_conteneurLes commandes indispensables
Section intitulée « Les commandes indispensables »1. docker logs — Voir ce que dit l’application
Section intitulée « 1. docker logs — Voir ce que dit l’application »# Logs completsdocker logs mon_conteneur
# Les 50 dernières lignesdocker logs --tail 50 mon_conteneur
# Suivre en temps réel (comme tail -f)docker logs -f mon_conteneur
# Avec timestampsdocker logs -t mon_conteneur
# Depuis une datedocker logs --since "2024-01-20T10:00:00" mon_conteneur
# Conteneur qui redémarre en boucle ? Combiner les deux :docker logs --tail 50 --since 5m mon_conteneurCe qu’il faut chercher : messages d’erreur, stack traces, “permission denied”, “connection refused”.
2. docker inspect — Voir la configuration complète
Section intitulée « 2. docker inspect — Voir la configuration complète »# Tout voir (JSON volumineux)docker inspect mon_conteneur
# Extraire des infos spécifiques avec Go templatesdocker inspect --format '{{.State.Status}}' mon_conteneurdocker inspect --format '{{.State.ExitCode}}' mon_conteneurdocker inspect --format '{{.State.OOMKilled}}' mon_conteneurdocker inspect --format '{{.RestartCount}}' mon_conteneurdocker inspect --format '{{.State.Error}}' mon_conteneurdocker inspect --format '{{json .Config.Entrypoint}}' mon_conteneurdocker inspect --format '{{json .Config.Cmd}}' mon_conteneurdocker inspect --format '{{.NetworkSettings.IPAddress}}' mon_conteneurdocker inspect --format '{{.HostConfig.Memory}}' mon_conteneurInfos clés à vérifier :
| Champ | Ce qu’il révèle |
|---|---|
.State.OOMKilled | true si tué par manque de mémoire |
.State.ExitCode | Le code de sortie (137, 143, etc.) |
.State.Error | Message d’erreur Docker (souvent révélateur) |
.RestartCount | Nombre de redémarrages (boucle infinie ?) |
.Config.Entrypoint / .Config.Cmd | Ce que le conteneur essaie de lancer |
.HostConfig.Memory | Limite mémoire configurée (0 = illimitée) |
.Mounts | Volumes montés (permissions ?) |
3. docker events — Voir ce qui se passe en temps réel
Section intitulée « 3. docker events — Voir ce qui se passe en temps réel »# Suivre tous les événements Dockerdocker events
# Filtrer par conteneurdocker events --filter container=mon_conteneur
# Filtrer par type d'événementdocker events --filter event=diedocker events --filter event=oomÉvénements importants :
start/die: démarrage / arrêtoom: out of memorykill: signal envoyéhealth_status: changement d’état healthcheck
4. docker stats — Voir la consommation de ressources
Section intitulée « 4. docker stats — Voir la consommation de ressources »# Stats en temps réeldocker stats
# Stats d'un conteneur spécifiquedocker stats mon_conteneur
# Une seule mesure (pas de stream)docker stats --no-stream mon_conteneurColonnes à surveiller :
- MEM USAGE / LIMIT : consommation vs limite (si proche = risque OOM)
- CPU % : utilisation CPU
- NET I/O : trafic réseau
5. docker debug — Débugger les conteneurs slim (Docker Desktop)
Section intitulée « 5. docker debug — Débugger les conteneurs slim (Docker Desktop) »Vous avez un conteneur basé sur une image distroless ou scratch ? Ces images ultra-légères n’ont ni shell, ni curl, ni aucun outil de diagnostic. Impossible de faire docker exec -it ... /bin/sh.
C’est là qu’intervient docker debug, une commande introduite en 2024 (GA dans Docker Desktop 4.33). Elle attache un shell de debug à n’importe quel conteneur, même ceux sans shell.
# Attacher un shell de debug à un conteneur slimdocker debug mon_conteneur
# Debug un conteneur arrêté (!)docker debug mon_conteneur_arrete
# Debug une image (sans la lancer)docker debug nginx:alpineLe shell de debug inclut des outils préinstallés : vim, nano, curl, wget, htop, netstat, ps, strace. Vous pouvez même installer des outils supplémentaires via le gestionnaire de paquets NixOS intégré, et vos customisations persistent entre les sessions.
Diagnostic approfondi : le cas OOM Kill
Section intitulée « Diagnostic approfondi : le cas OOM Kill »L’exit code 137 est l’un des plus fréquents et des plus frustrants. Il signifie que votre conteneur a reçu un signal SIGKILL (9). Les causes possibles :
- OOM kill Docker : le conteneur a dépassé sa limite mémoire (
--memory) - OOM kill hôte : le serveur manque de RAM (même sans limite Docker)
docker kill: quelqu’un (ou un script) a tué le conteneur- Supervision externe :
systemd-oomd, watchdog, orchestrateur
Pourquoi l’OOM kill arrive ?
Section intitulée « Pourquoi l’OOM kill arrive ? »Chaque conteneur peut avoir une limite mémoire. Quand l’application dépasse cette limite, le kernel Linux intervient : il tue le processus pour protéger le système. C’est brutal, sans prévenir, et souvent sans laisser de logs dans l’application.
Étape 1 : Confirmer l’OOM kill
Section intitulée « Étape 1 : Confirmer l’OOM kill »# Vérifier si le conteneur a été tué par OOMdocker inspect --format='{{.State.OOMKilled}}' mon_conteneur# true = OOM kill confirmé# false = autre cause (docker kill manuel, signal externe)Étape 2 : Voir les limites configurées
Section intitulée « Étape 2 : Voir les limites configurées »# Limite mémoire du conteneur (en octets, 0 = pas de limite)docker inspect --format='{{.HostConfig.Memory}}' mon_conteneur
# En format lisible avec docker statsdocker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}" mon_conteneur# Résultat : mon_conteneur 450MiB / 512MiBSi la consommation est proche de la limite, votre conteneur est en danger d’OOM.
Étape 3 : Consulter les logs kernel
Section intitulée « Étape 3 : Consulter les logs kernel »Le kernel Linux laisse des traces quand il tue un processus. Ces logs ne sont pas dans docker logs, mais dans les logs système :
# Sur Linux, chercher les OOM kills récentssudo dmesg | grep -i "killed process"# ousudo journalctl -k | grep -i "oom"
# Résultat typique :# Out of memory: Killed process 12345 (node) total-vm:1204624kB...Solutions à l’OOM kill
Section intitulée « Solutions à l’OOM kill »Option 1 : Augmenter la limite mémoire
# Lancer avec plus de mémoiredocker run -d --memory=1g mon_app
# Modifier un conteneur existantdocker update --memory=1g mon_conteneurOption 2 : Optimiser l’application — fuite mémoire ? Trop de workers ? Cache non borné ?
Option 3 : Ajouter du swap (déconseillé en prod, mais peut dépanner)
# Mémoire + swap (le conteneur peut utiliser 512m RAM + 512m swap)docker run -d --memory=512m --memory-swap=1g mon_appStop vs Kill : quelle différence ?
Section intitulée « Stop vs Kill : quelle différence ? »| Commande | Signal envoyé | Comportement |
|---|---|---|
docker stop | SIGTERM, puis SIGKILL après timeout | Arrêt propre (grace period de 10s par défaut) |
docker kill | SIGKILL immédiat | Arrêt brutal, pas de nettoyage |
Quand utiliser quoi ?
docker stop: toujours par défaut. Laisse l’application fermer proprement les connexions, sauvegarder les données, etc.docker kill: sidocker stopne fonctionne pas (processus bloqué) ou en urgence.
# Arrêt propre (attend 10s max)docker stop mon_conteneur
# Arrêt propre avec timeout personnalisé (30s)docker stop -t 30 mon_conteneur
# Arrêt brutal immédiatdocker kill mon_conteneurPourquoi docker stop ne fonctionne pas parfois ?
Section intitulée « Pourquoi docker stop ne fonctionne pas parfois ? »Le problème vient souvent du PID 1. Dans un conteneur, le processus principal (PID 1) doit relayer les signaux aux processus enfants. Si votre script shell lance une application sans exec, le signal SIGTERM est reçu par le shell… qui l’ignore.
Solutions :
# Solution 1 : Utiliser --init (ajoute un mini init qui gère les signaux)docker run --init mon_image
# Solution 2 : Dans le Dockerfile, utiliser exec dans le script# ❌ Mauvais : ./mon_app# ✅ Bon : exec ./mon_app
# Solution 3 : Changer le signal d'arrêt (si l'app attend autre chose)# Dans le Dockerfile :# STOPSIGNAL SIGINTChecklist de debug réseau
Section intitulée « Checklist de debug réseau »Quand un conteneur ne communique pas avec un autre ou avec l’extérieur :
-
Tester depuis l’hôte (port mapping)
Fenêtre de terminal # Vérifier le mapping de portsdocker port mon_conteneur# Résultat attendu : 8080/tcp -> 0.0.0.0:8080# Tester depuis l'hôtecurl http://localhost:8080 -
Vérifier que le conteneur est sur le bon réseau
Fenêtre de terminal docker network inspect mon_reseau# Chercher le conteneur dans "Containers" -
Vérifier l’IP du conteneur
Fenêtre de terminal docker inspect --format '{{.NetworkSettings.IPAddress}}' mon_conteneur -
Tester la connectivité entre conteneurs
Fenêtre de terminal docker exec autre_conteneur ping mon_conteneurdocker exec autre_conteneur curl http://mon_conteneur:8080 -
Vérifier la résolution DNS
Fenêtre de terminal # Le DNS interne Docker doit résoudre les noms de conteneursdocker exec mon_conteneur cat /etc/resolv.confdocker exec mon_conteneur nslookup autre_conteneur -
Vérifier que l’application écoute sur 0.0.0.0
Fenêtre de terminal docker exec mon_conteneur netstat -tlnp# ou avec ss :docker exec mon_conteneur ss -tlnp# L'app doit écouter sur 0.0.0.0:PORT, pas 127.0.0.1:PORT
Les 3 causes les plus fréquentes :
| Symptôme | Cause probable | Solution |
|---|---|---|
| Timeout depuis l’hôte | Port mapping absent ou incorrect | Ajouter -p 8080:80 |
| Timeout entre conteneurs | Pas sur le même réseau | Connecter au même network |
| ”Connection refused” | App écoute sur 127.0.0.1 | Configurer l’app sur 0.0.0.0 |
Testez vos connaissances
Section intitulée « Testez vos connaissances »Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
📋 Récapitulatif de vos réponses
Vérifiez vos réponses avant de soumettre. Cliquez sur une question pour la modifier.
Détail des réponses
À retenir
Section intitulée « À retenir »- Checklist express :
docker ps -a→docker logs→docker inspect(ExitCode, OOMKilled, RestartCount) - Exit code 137 = SIGKILL (OOM, docker kill, ou supervision externe) — vérifiez
OOMKilledetdmesg - Exit code 143 = arrêt propre via
docker stop, c’est normal docker logsvide ? Vérifiez le log driver (json-filevssyslog/journald)- RestartCount élevé = boucle de crash — désactivez temporairement le restart pour inspecter
docker stopbloqué ? Problème de PID 1 — utilisez--initouexecdans vos scripts- HEALTHCHECK = détecte quand l’app est vivante mais ne répond plus
- Réseau : les 3 causes = port mapping, réseau différent, app sur 127.0.0.1
- Images slim : utilisez
netshootoudocker debug(Docker Desktop Pro/Team/Business)
Pour aller plus loin
Section intitulée « Pour aller plus loin »Prochaine étape
Section intitulée « Prochaine étape »Maintenant que vous savez diagnostiquer un conteneur, apprenez à écrire des Dockerfiles optimisés pour éviter les problèmes en amont.