Aller au contenu
high

Exécuter et dépanner : cycle de vie, logs, inspect, events

26 min de lecture

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.

Vous êtes pressé ? Voici les 5 commandes à lancer immédiatement :

Fenêtre de terminal
# 1. État et exit code
docker 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 commande
docker 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_conteneur

La suite du guide détaille chaque commande et explique comment interpréter les résultats.

  • 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

Un conteneur passe par plusieurs états au cours de sa vie. Comprendre ces états est la première étape du diagnostic.

Cycle de vie d'un conteneur Docker : états created, running, paused, exited, removed et restarting

ÉtatDescriptionCommande qui y mène
createdConteneur créé mais pas démarrédocker create
runningEn cours d’exécutiondocker start / docker run
pausedProcessus gelés (mais en mémoire)docker pause
exitedProcessus terminé (code de sortie disponible)docker stop / crash
restartingEn cours de redémarragePolitique de restart
removingEn cours de suppressiondocker rm
Fenêtre de terminal
# Voir l'état actuel d'un conteneur
docker ps -a --filter "name=mon_conteneur"
# Résultat typique
CONTAINER ID IMAGE STATUS NAMES
abc123 nginx Exited (137) 2 hours ago mon_conteneur

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 :

Fenêtre de terminal
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 :

Fenêtre de terminal
# Récupérer l'exit code seul
docker inspect --format='{{.State.ExitCode}}' mon_app
# Résultat : 137
# Récupérer plusieurs infos d'un coup
docker inspect --format='Exit: {{.State.ExitCode}}, OOM: {{.State.OOMKilled}}' mon_app
# Résultat : Exit: 137, OOM: true

Méthode 3 : filtrer par exit code — Utile pour trouver tous les conteneurs qui ont crashé :

Fenêtre de terminal
# 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 lisible
docker ps -a --filter "status=exited" --format "table {{.Names}}\t{{.Status}}\t{{.Image}}"
Exit codeSignificationCause probable
0SuccèsLe processus s’est terminé normalement
1Erreur génériqueBug dans l’application, mauvaise config
126Permission deniedLe binaire n’est pas exécutable
127Command not foundLa commande n’existe pas dans l’image
137SIGKILL (128+9)OOM kill ou docker kill
143SIGTERM (128+15)Arrêt propre via docker stop
139SIGSEGV (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

Utilisez ce schéma pour diagnostiquer rapidement la cause d’un arrêt :

Arbre de décision pour diagnostiquer un conteneur qui ne démarre pas selon son exit code

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.

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

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 1

Dé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 passer unhealthy. Évite les faux positifs sur un pic de charge.

Une fois le HEALTHCHECK configuré, vous pouvez surveiller l’état de votre conteneur :

Fenêtre de terminal
# Voir l'état de santé actuel
docker 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 unhealthy
docker ps --filter health=unhealthy

L’historique des checks vous montre les sorties de chaque vérification, ce qui est précieux pour comprendre pourquoi un conteneur est passé unhealthy.

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.

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.

Fenêtre de terminal
# Développement : pas de restart (voir les crashes)
docker run -d --restart no mon_app
# Production : restart sauf arrêt manuel
docker run -d --restart unless-stopped mon_app
# Service critique : toujours restart
docker run -d --restart always mon_app
# Limité à 5 tentatives sur erreur
docker run -d --restart on-failure:5 mon_app

Pour modifier la policy d’un conteneur existant sans le recréer :

Fenêtre de terminal
docker update --restart unless-stopped mon_conteneur

1. docker logs — Voir ce que dit l’application

Section intitulée « 1. docker logs — Voir ce que dit l’application »
Fenêtre de terminal
# Logs complets
docker logs mon_conteneur
# Les 50 dernières lignes
docker logs --tail 50 mon_conteneur
# Suivre en temps réel (comme tail -f)
docker logs -f mon_conteneur
# Avec timestamps
docker logs -t mon_conteneur
# Depuis une date
docker 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_conteneur

Ce 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 »
Fenêtre de terminal
# Tout voir (JSON volumineux)
docker inspect mon_conteneur
# Extraire des infos spécifiques avec Go templates
docker inspect --format '{{.State.Status}}' mon_conteneur
docker inspect --format '{{.State.ExitCode}}' mon_conteneur
docker inspect --format '{{.State.OOMKilled}}' mon_conteneur
docker inspect --format '{{.RestartCount}}' mon_conteneur
docker inspect --format '{{.State.Error}}' mon_conteneur
docker inspect --format '{{json .Config.Entrypoint}}' mon_conteneur
docker inspect --format '{{json .Config.Cmd}}' mon_conteneur
docker inspect --format '{{.NetworkSettings.IPAddress}}' mon_conteneur
docker inspect --format '{{.HostConfig.Memory}}' mon_conteneur

Infos clés à vérifier :

ChampCe qu’il révèle
.State.OOMKilledtrue si tué par manque de mémoire
.State.ExitCodeLe code de sortie (137, 143, etc.)
.State.ErrorMessage d’erreur Docker (souvent révélateur)
.RestartCountNombre de redémarrages (boucle infinie ?)
.Config.Entrypoint / .Config.CmdCe que le conteneur essaie de lancer
.HostConfig.MemoryLimite mémoire configurée (0 = illimitée)
.MountsVolumes 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 »
Fenêtre de terminal
# Suivre tous les événements Docker
docker events
# Filtrer par conteneur
docker events --filter container=mon_conteneur
# Filtrer par type d'événement
docker events --filter event=die
docker events --filter event=oom

Événements importants :

  • start / die : démarrage / arrêt
  • oom : out of memory
  • kill : 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 »
Fenêtre de terminal
# Stats en temps réel
docker stats
# Stats d'un conteneur spécifique
docker stats mon_conteneur
# Une seule mesure (pas de stream)
docker stats --no-stream mon_conteneur

Colonnes à 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.

Fenêtre de terminal
# Attacher un shell de debug à un conteneur slim
docker debug mon_conteneur
# Debug un conteneur arrêté (!)
docker debug mon_conteneur_arrete
# Debug une image (sans la lancer)
docker debug nginx:alpine

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

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

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.

Fenêtre de terminal
# Vérifier si le conteneur a été tué par OOM
docker inspect --format='{{.State.OOMKilled}}' mon_conteneur
# true = OOM kill confirmé
# false = autre cause (docker kill manuel, signal externe)
Fenêtre de terminal
# Limite mémoire du conteneur (en octets, 0 = pas de limite)
docker inspect --format='{{.HostConfig.Memory}}' mon_conteneur
# En format lisible avec docker stats
docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}" mon_conteneur
# Résultat : mon_conteneur 450MiB / 512MiB

Si la consommation est proche de la limite, votre conteneur est en danger d’OOM.

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 :

Fenêtre de terminal
# Sur Linux, chercher les OOM kills récents
sudo dmesg | grep -i "killed process"
# ou
sudo journalctl -k | grep -i "oom"
# Résultat typique :
# Out of memory: Killed process 12345 (node) total-vm:1204624kB...

Option 1 : Augmenter la limite mémoire

Fenêtre de terminal
# Lancer avec plus de mémoire
docker run -d --memory=1g mon_app
# Modifier un conteneur existant
docker update --memory=1g mon_conteneur

Option 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)

Fenêtre de terminal
# Mémoire + swap (le conteneur peut utiliser 512m RAM + 512m swap)
docker run -d --memory=512m --memory-swap=1g mon_app
CommandeSignal envoyéComportement
docker stopSIGTERM, puis SIGKILL après timeoutArrêt propre (grace period de 10s par défaut)
docker killSIGKILL immédiatArrê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 : si docker stop ne fonctionne pas (processus bloqué) ou en urgence.
Fenêtre de terminal
# 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édiat
docker kill mon_conteneur

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 :

Fenêtre de terminal
# 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 SIGINT

Quand un conteneur ne communique pas avec un autre ou avec l’extérieur :

  1. Tester depuis l’hôte (port mapping)

    Fenêtre de terminal
    # Vérifier le mapping de ports
    docker port mon_conteneur
    # Résultat attendu : 8080/tcp -> 0.0.0.0:8080
    # Tester depuis l'hôte
    curl http://localhost:8080
  2. 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"
  3. Vérifier l’IP du conteneur

    Fenêtre de terminal
    docker inspect --format '{{.NetworkSettings.IPAddress}}' mon_conteneur
  4. Tester la connectivité entre conteneurs

    Fenêtre de terminal
    docker exec autre_conteneur ping mon_conteneur
    docker exec autre_conteneur curl http://mon_conteneur:8080
  5. Vérifier la résolution DNS

    Fenêtre de terminal
    # Le DNS interne Docker doit résoudre les noms de conteneurs
    docker exec mon_conteneur cat /etc/resolv.conf
    docker exec mon_conteneur nslookup autre_conteneur
  6. 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ômeCause probableSolution
Timeout depuis l’hôtePort mapping absent ou incorrectAjouter -p 8080:80
Timeout entre conteneursPas sur le même réseauConnecter au même network
”Connection refused”App écoute sur 127.0.0.1Configurer l’app sur 0.0.0.0

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

7 questions
5 min.
80%

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

  1. Checklist express : docker ps -adocker logsdocker inspect (ExitCode, OOMKilled, RestartCount)
  2. Exit code 137 = SIGKILL (OOM, docker kill, ou supervision externe) — vérifiez OOMKilled et dmesg
  3. Exit code 143 = arrêt propre via docker stop, c’est normal
  4. docker logs vide ? Vérifiez le log driver (json-file vs syslog/journald)
  5. RestartCount élevé = boucle de crash — désactivez temporairement le restart pour inspecter
  6. docker stop bloqué ? Problème de PID 1 — utilisez --init ou exec dans vos scripts
  7. HEALTHCHECK = détecte quand l’app est vivante mais ne répond plus
  8. Réseau : les 3 causes = port mapping, réseau différent, app sur 127.0.0.1
  9. Images slim : utilisez netshoot ou docker debug (Docker Desktop Pro/Team/Business)

Maintenant que vous savez diagnostiquer un conteneur, apprenez à écrire des Dockerfiles optimisés pour éviter les problèmes en amont.