
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 si l'application gère SIGTERM (grace period de 10s par défaut, voir le piège PID 1 plus bas) |
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 du PID 1 et d'une règle méconnue du noyau Linux : un processus PID 1 ne reçoit que les signaux pour lesquels il a installé un handler. SIGTERM, sans handler explicite, est donc ignoré par PID 1, alors qu'il tuerait n'importe quel autre processus. Une application lancée en PID 1 qui ne gère pas SIGTERM ne voit jamais le docker stop : Docker attend les 10 secondes du grace period dans le vide, puis envoie SIGKILL. La « fermeture propre » n'a pas lieu, les connexions et écritures en cours sont coupées net.
Le cas du script shell sans exec est une variante du même piège : le shell devient PID 1, n'a pas de handler SIGTERM, et n'exécute donc jamais votre application au premier plan. exec ./mon_app remplace le shell par l'application, qui devient elle-même PID 1 (et doit alors gérer SIGTERM), tandis que --init insère un vrai init (tini) qui, lui, relaie proprement les signaux.
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
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
À 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.