Logs, shells et exec : techniques pratiques pour déboguer des conteneurs Docker en cours d’exécution
Déboguer un conteneur en cours d’exécution est une compétence essentielle : l’application tourne déjà (ou redémarre en boucle), les symptômes sont visibles, et vous devez comprendre ce qui se passe réellement à l’intérieur, sans forcément reconstruire une image ni déployer un environnement de dev complet.
Ce tutoriel se concentre sur trois axes très pratiques :
- Logs : observer le comportement, les erreurs, les redémarrages, les sorties stdout/stderr.
- Shells : obtenir une session interactive (quand c’est possible) pour inspecter le système de fichiers, les processus, le réseau, les variables d’environnement.
docker exec: exécuter des commandes ciblées dans un conteneur vivant, avec des astuces pour diagnostiquer vite, même quand l’image est minimaliste.
L’objectif : passer de “ça ne marche pas” à un diagnostic reproductible, avec des commandes réelles et une méthode.
Prérequis et contexte
- Docker installé (Linux/macOS/Windows).
- Accès à un terminal.
- Quelques notions de base (images, conteneurs, ports).
Pour les exemples, on utilisera des conteneurs typiques (Nginx, Alpine, Node, etc.). Adaptez les noms de conteneurs à votre cas.
1) Identifier le conteneur à diagnostiquer
Avant toute chose, listez les conteneurs et repérez celui qui pose problème.
docker ps
Pour inclure les conteneurs arrêtés (utile en cas de crash loop) :
docker ps -a
Colonnes importantes :
- STATUS :
Up ...(en cours),Exited (...)(arrêté),Restarting(redémarrage en boucle). - NAMES : nom pratique pour les commandes suivantes.
- PORTS : mappages de ports (utile pour vérifier l’exposition).
Astuce : filtrer par nom ou image :
docker ps --filter "name=api"
docker ps --filter "ancestor=nginx:alpine"
2) Les logs : votre première source de vérité
2.1 Comprendre ce que docker logs montre (et ne montre pas)
docker logs affiche ce que le processus principal du conteneur écrit sur stdout/stderr. C’est crucial :
- Si votre application logue dans un fichier interne (ex:
/var/log/app.log) sans rediriger vers stdout,docker logspeut être vide. - Si vous utilisez un driver de logs spécifique (journald, fluentd, etc.), le comportement peut varier.
Afficher les logs :
docker logs <conteneur>
Afficher les dernières lignes :
docker logs --tail 200 <conteneur>
Suivre les logs en temps réel (mode “tail -f”) :
docker logs -f <conteneur>
Ajouter des timestamps :
docker logs -f --timestamps <conteneur>
Limiter à une période :
docker logs --since 10m <conteneur>
docker logs --since "2026-03-04T10:00:00" <conteneur>
2.2 Diagnostiquer un conteneur qui redémarre en boucle
Un conteneur en Restarting est souvent un processus qui crash immédiatement (exit code non nul) ou un healthcheck qui échoue selon la politique de redémarrage.
- Vérifiez le statut :
docker ps --filter "name=<conteneur>"
- Consultez les logs avec
--tailet--timestamps:
docker logs --tail 200 --timestamps <conteneur>
- Inspectez le code de sortie du dernier arrêt :
docker inspect -f '{{.State.ExitCode}}' <conteneur>
docker inspect -f '{{.State.Error}}' <conteneur>
- Regardez si un healthcheck échoue :
docker inspect -f '{{json .State.Health}}' <conteneur>
Si Health.Status est unhealthy, vous avez un indice solide : l’app tourne peut-être, mais ne répond pas comme attendu.
2.3 Trucs pratiques pour “lire” des logs efficacement
- Chercher des patterns (erreurs, stack traces) :
docker logs <conteneur> 2>&1 | grep -iE "error|exception|fatal|panic"
- Capturer les logs dans un fichier (pour partager ou analyser) :
docker logs --timestamps <conteneur> > logs_conteneur.txt
- Comparer plusieurs conteneurs (ex: web + worker) :
docker logs -f web
# dans un autre terminal
docker logs -f worker
3) docker exec : exécuter des commandes dans un conteneur vivant
3.1 Les bases : exécuter une commande simple
docker exec <conteneur> uname -a
docker exec <conteneur> ps aux
docker exec <conteneur> env
Si vous avez besoin des privilèges root (selon l’image) :
docker exec -u 0 <conteneur> id
docker exec -u 0 <conteneur> ls -la /root
3.2 Mode interactif : obtenir un shell
Le classique :
docker exec -it <conteneur> sh
Si bash est disponible :
docker exec -it <conteneur> bash
Beaucoup d’images minimalistes (Alpine, distroless, scratch) n’ont pas bash. Essayez dans cet ordre :
docker exec -it <conteneur> sh
docker exec -it <conteneur> ash
docker exec -it <conteneur> bash
Important : docker exec ne fonctionne que si le conteneur est en cours d’exécution. Si le conteneur est arrêté, il faut soit le relancer, soit utiliser une autre approche (voir section “conteneurs minimalistes / distroless”).
3.3 exec vs attach : différence utile
docker execlance un nouveau processus dans le conteneur.docker attachse connecte au processus principal (PID 1) et à son stdin/stdout.
En pratique :
- Utilisez
execpour diagnostiquer sans perturber le processus principal. - Utilisez
attachsi vous devez interagir avec l’entrée standard du processus principal (rare en prod).
Exemple :
docker attach <conteneur>
# Pour se détacher sans arrêter : Ctrl-p puis Ctrl-q
4) Inspection “système” depuis l’intérieur : checklist de débogage
Une fois dans le conteneur (via docker exec -it ... sh), vous pouvez appliquer une checklist.
4.1 Vérifier les processus (PID, arguments, consommation)
Selon les outils présents :
ps aux
# ou
ps -ef
Si ps n’existe pas, essayez :
cat /proc/1/cmdline | tr '\0' ' '
Voir l’arborescence des processus (si pstree est installé) :
pstree -ap
Identifier le processus qui écoute sur un port :
- Avec
ss:
ss -lntp
- Ou
netstat(si présent) :
netstat -lntp
4.2 Vérifier les variables d’environnement réellement présentes
env | sort
Souvent, un bug vient d’une variable manquante, mal nommée, ou d’un secret non monté.
Vérifiez aussi ce que voit le processus principal :
tr '\0' '\n' < /proc/1/environ | sort
4.3 Vérifier le système de fichiers : volumes, permissions, fichiers attendus
Lister les montages :
mount | head -n 50
Vérifier un répertoire critique :
ls -la /app
ls -la /data
Diagnostiquer un problème de permissions :
id
whoami
ls -ld /data
touch /data/test_write && echo "OK" || echo "ECHEC"
Si l’écriture échoue, vous avez un indice : utilisateur non-root, volume en lecture seule, UID/GID incohérents, etc.
4.4 Vérifier la résolution DNS et la connectivité réseau
Selon les outils disponibles :
- DNS :
cat /etc/resolv.conf
cat /etc/hosts
- Tester une résolution :
getent hosts example.com
# ou
nslookup example.com
# ou
dig example.com
- Tester un appel HTTP :
curl -v http://service:8080/health
# ou
wget -S -O- http://service:8080/health
- Tester un port TCP (si
nc/netcat est présent) :
nc -vz service 5432
Si vous n’avez aucun outil réseau (cas fréquent), voir la section “outils de debug éphémères”.
5) docker inspect : comprendre la configuration effective (sans entrer dans le conteneur)
docker inspect est indispensable pour vérifier :
- Variables d’environnement injectées
- Ports exposés et mappés
- Volumes montés
- Réseau, IP
- Commande/entrypoint
- Healthcheck
- Politique de redémarrage
Afficher un JSON complet :
docker inspect <conteneur>
Extraire des champs utiles :
- Commande et entrypoint :
docker inspect -f 'Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}}' <conteneur>
- Variables d’environnement :
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' <conteneur>
- Montages :
docker inspect -f '{{range .Mounts}}{{println .Type .Source "->" .Destination "rw=" .RW}}{{end}}' <conteneur>
- Réseau et IP :
docker inspect -f '{{range .NetworkSettings.Networks}}{{println .NetworkID .IPAddress}}{{end}}' <conteneur>
- Ports :
docker port <conteneur>
6) Déboguer un problème de ports : “ça tourne mais je n’y accède pas”
Symptôme : le conteneur est Up, les logs semblent OK, mais le service est inaccessible.
6.1 Vérifier le mapping de ports côté hôte
docker ps --filter "name=<conteneur>"
docker port <conteneur>
Exemple de sortie :
80/tcp -> 0.0.0.0:8080
Cela signifie : accéder via http://localhost:8080.
6.2 Vérifier l’écoute du service dans le conteneur
Dans le conteneur :
ss -lntp
Piège courant : l’application écoute sur 127.0.0.1 au lieu de 0.0.0.0. Dans un conteneur, écouter sur 127.0.0.1 rend le service inaccessible depuis l’extérieur du conteneur.
Vous voulez voir quelque chose comme :
LISTEN 0 4096 0.0.0.0:8080 ...
Si vous voyez 127.0.0.1:8080, corrigez la config de bind (par ex. --host 0.0.0.0).
6.3 Tester depuis l’hôte
curl -v http://localhost:8080/
Si ça échoue, testez l’IP du conteneur (utile sur Linux) :
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' <conteneur>
curl -v http://<ip_conteneur>:8080/
7) Déboguer un problème de dépendance : base de données, cache, API externe
7.1 Vérifier que les conteneurs sont sur le même réseau
Lister les réseaux :
docker network ls
Inspecter un réseau :
docker network inspect <reseau>
Vérifier l’appartenance :
docker inspect -f '{{json .NetworkSettings.Networks}}' <conteneur>
Si deux services doivent communiquer via un nom DNS (ex: db), ils doivent être sur le même réseau Docker (souvent un réseau bridge créé par Compose).
7.2 Tester la connectivité depuis le conteneur applicatif
Dans le conteneur :
getent hosts db
nc -vz db 5432
Puis tester l’authentification (ex PostgreSQL si psql est présent) :
psql -h db -U myuser -d mydb -c 'select 1;'
Si psql n’est pas installé, vous pouvez au moins tester TCP (nc) et vérifier les variables :
env | grep -iE 'db|postgres|pg|database|redis'
8) Images minimalistes et “distroless” : comment déboguer sans shell
Certaines images n’ont ni shell, ni outils (ps, ls, cat, curl). C’est fréquent en production (images distroless) pour réduire la surface d’attaque.
8.1 Stratégie 1 : utiliser un conteneur “toolbox” dans le même réseau
L’idée : lancer un conteneur temporaire avec des outils réseau (curl, dig, etc.) sur le même réseau Docker, pour tester la connectivité vers votre service.
Trouver le réseau de votre conteneur :
docker inspect -f '{{range $k, $v := .NetworkSettings.Networks}}{{println $k}}{{end}}' <conteneur>
Lancer un conteneur debug (ex: netshoot, très pratique) :
docker run --rm -it --network <reseau> nicolaka/netshoot bash
Depuis ce conteneur, vous pouvez faire :
curl -v http://mon-service:8080/health
dig mon-service
ss -lntp
Alternative plus légère :
docker run --rm -it --network <reseau> alpine:3.20 sh
apk add --no-cache curl bind-tools netcat-openbsd
8.2 Stratégie 2 : entrer dans l’espace de noms du conteneur (Linux)
Sur Linux, vous pouvez utiliser nsenter (côté hôte) pour entrer dans les namespaces du conteneur, utile si docker exec est limité par l’absence de shell/outils.
Récupérer le PID du conteneur :
docker inspect -f '{{.State.Pid}}' <conteneur>
Entrer dans ses namespaces :
sudo nsenter -t <PID> -n -p -m -u -i sh
Vous utilisez alors les outils de l’hôte, mais “dans” le contexte réseau/process du conteneur. C’est puissant, mais à manier avec prudence (et nécessite des privilèges).
8.3 Stratégie 3 : ajouter temporairement des outils (si votre politique le permet)
En environnement non-prod, vous pouvez reconstruire une image avec des outils de debug (curl, bash, etc.). En prod, évitez de modifier l’image juste pour déboguer, préférez les méthodes “toolbox”.
9) Déboguer les permissions et l’utilisateur (problème très courant)
9.1 Vérifier l’utilisateur effectif
Dans le conteneur :
id
whoami
Depuis l’hôte :
docker inspect -f '{{.Config.User}}' <conteneur>
Si l’application écrit dans un volume monté, assurez-vous que l’UID/GID a les droits.
9.2 Diagnostiquer un volume monté en lecture seule
Depuis l’hôte :
docker inspect -f '{{range .Mounts}}{{println .Destination "rw=" .RW}}{{end}}' <conteneur>
Dans le conteneur :
touch /chemin/volume/test && echo OK || echo ECHEC
Si ECHEC, regardez les permissions :
ls -ld /chemin/volume
10) Comprendre le rôle du PID 1 et les signaux (arrêts “bizarres”)
Dans un conteneur, le processus principal est PID 1. Il a des comportements particuliers vis-à-vis des signaux (SIGTERM, SIGINT) et de la gestion des zombies si aucun init n’est présent.
10.1 Voir ce qui tourne en PID 1
Dans le conteneur :
ps -p 1 -o pid,comm,args
Ou depuis l’hôte :
docker inspect -f '{{.Path}} {{join .Args " "}}' <conteneur>
10.2 Tester un arrêt propre
docker stop <conteneur>
Docker envoie SIGTERM puis SIGKILL après un délai (10s par défaut). Ajuster :
docker stop -t 30 <conteneur>
Si votre application ne s’arrête pas proprement, les logs peuvent montrer des corruptions, des écritures interrompues, etc.
11) Cas pratique : diagnostiquer une API qui renvoie 502 derrière Nginx
Scénario : Nginx reverse proxy répond 502, l’API semble instable.
11.1 Vérifier les logs Nginx
docker logs --tail 200 --timestamps nginx
Cherchez des erreurs du type “connect() failed (111: Connection refused)”.
11.2 Vérifier l’API
Logs API :
docker logs --tail 200 --timestamps api
Si l’API crash, vous le verrez souvent ici.
11.3 Tester la connectivité depuis Nginx vers l’API
Entrer dans Nginx :
docker exec -it nginx sh
Tester l’API :
# si curl est présent
curl -v http://api:8080/health
Si curl n’est pas présent, utilisez un conteneur toolbox sur le même réseau (section 8.1).
11.4 Vérifier que Nginx pointe vers le bon host/port
Dans Nginx :
nginx -T | sed -n '1,200p'
Repérez proxy_pass http://api:8080; et confirmez que l’API écoute bien sur 8080 et sur 0.0.0.0.
12) Bonnes pratiques pour rendre le débogage plus facile (sans “ouvrir” la prod)
Même si ce tutoriel traite du débogage “en live”, vous pouvez rendre la situation beaucoup plus simple à l’avance.
12.1 Logger sur stdout/stderr
- Configurez votre application pour écrire sur stdout/stderr.
- Pour Nginx, par exemple, envoyez
access_logeterror_logvers/dev/stdoutet/dev/stderr(selon l’image et la config).
12.2 Ajouter un endpoint de healthcheck
Un /health ou /ready qui vérifie les dépendances principales (DB, cache) et renvoie un statut clair.
12.3 Exposer des métriques (si possible)
Même un minimum (latence, erreurs) aide à corréler avec les logs.
12.4 Garder une “toolbox” de debug approuvée
Avoir une image interne (ou l’usage de nicolaka/netshoot) validée par votre sécurité, pour diagnostiquer sans modifier les services.
13) Commandes récapitulatives (antisèche)
Logs
docker logs <c>
docker logs -f --timestamps <c>
docker logs --tail 200 <c>
docker logs --since 10m <c>
Exec / Shell
docker exec <c> <cmd>
docker exec -it <c> sh
docker exec -it -u 0 <c> sh
Inspect
docker inspect <c>
docker inspect -f '{{.State.ExitCode}}' <c>
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' <c>
docker inspect -f '{{range .Mounts}}{{println .Source "->" .Destination "rw=" .RW}}{{end}}' <c>
docker port <c>
Réseau / toolbox
docker inspect -f '{{range $k, $v := .NetworkSettings.Networks}}{{println $k}}{{end}}' <c>
docker run --rm -it --network <reseau> nicolaka/netshoot bash
Conclusion : une méthode simple et robuste
- Commencez par les logs (
docker logsavec--tail,--timestamps,--since) pour établir une chronologie. - Inspectez la configuration effective (
docker inspect) : env, mounts, ports, healthcheck, restart policy. - Exécutez des commandes ciblées (
docker exec) pour valider vos hypothèses (ports en écoute, DNS, permissions, variables). - Si l’image est minimaliste, utilisez une toolbox sur le même réseau ou
nsenter(Linux) pour obtenir de la visibilité sans modifier le service.
Si vous me donnez :
- la sortie de
docker ps, docker logs --tail 200 --timestamps <conteneur>,- et
docker inspect <conteneur>(ou au moins entrypoint/cmd/env/mounts),
je peux vous proposer un plan de diagnostic adapté à votre cas précis.