Introduction
Déboguer un conteneur Docker mal configuré consiste rarement à trouver une commande magique. Il s'agit de construire une séquence reproductible : observer l'état actuel, identifier le plus petit changement possible, l'appliquer de manière contrôlée, vérifier le résultat et savoir revenir en arrière si la situation empire. Ce guide passe en revue les erreurs de configuration courantes — réseau, montages, variables d'environnement, contrôles de santé et limites de ressources — en utilisant des commandes réelles et des sorties attendues pour passer rapidement du symptôme à la solution.
Nous nous concentrerons sur un débogage pratique, orienté production. L'objectif est la sécurité opérationnelle : ne jamais modifier un conteneur en cours d'exécution à l'aveugle, protéger les secrets, limiter le rayon d'impact et toujours avoir un plan de récupération documenté avant d'en avoir besoin.
Inventaire de la version et de l'environnement
Avant de toucher à quoi que ce soit, sachez exactement avec quoi vous travaillez. Exécutez ces commandes en lecture seule et capturez la sortie dans vos notes d'incident avec un horodatage.
Vérifiez la version de Docker et les informations du démon :
docker version
La sortie attendue inclut les versions client et serveur. Par exemple :
Client: Docker Engine - Community
Version: 24.0.7
API version: 1.43
Go version: go1.20.10
Git commit: 311b9ff
Built: Thu Oct 26 09:08:15 2023
OS/Arch: linux/amd64
Context: default
Server: Docker Engine - Community
Engine:
Version: 24.0.7
Si les versions client et serveur diffèrent de manière significative, certaines fonctionnalités peuvent se comporter de manière inattendue. Mettez à niveau ou rétrogradez pour correspondre au standard de l'équipe.
Listez les conteneurs en cours d'exécution avec les métadonnées clés :
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}\t{{.Image}}"
Exemple de résultat :
NAMES STATUS PORTS IMAGE
web-app Up 2 hours (unhealthy) 0.0.0.0:8080->80/tcp nginx:1.25
postgres-db Up 5 hours 0.0.0.0:5432->5432/tcp postgres:15
Notez l'état de santé. Un conteneur « unhealthy » est un indice, pas un verdict — il peut encore servir du trafic tout en échouant à son contrôle de santé.
Inspectez un conteneur spécifique pour les montages, réseaux, variables d'environnement et configuration de santé :
docker inspect web-app
Cela produit un gros objet JSON. Filtrez les parties dont vous avez besoin :
docker inspect web-app --format '{{json .Mounts}}' | jq
docker inspect web-app --format '{{json .NetworkSettings.Networks}}' | jq
docker inspect web-app --format '{{json .Config.Env}}' | jq
docker inspect web-app --format '{{json .Config.Healthcheck}}' | jq
Chaque commande renvoie des données structurées. Par exemple, la sortie des montages pour un conteneur avec un volume nommé et un montage bind pourrait ressembler à :
[
{
"Type": "volume",
"Name": "web-app-data",
"Source": "/var/lib/docker/volumes/web-app-data/_data",
"Destination": "/var/www/html",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
},
{
"Type": "bind",
"Source": "/home/user/config",
"Destination": "/etc/app/config",
"Mode": "ro",
"RW": false,
"Propagation": "rprivate"
}
]
Portez une attention particulière au champ RW : un montage bind en lecture seule fera échouer les écritures avec une erreur de permission si l'application s'attend à modifier ces fichiers.
Pour les projets Compose, obtenez une vue globale du projet :
docker compose ps
docker compose config --services
docker compose config --volumes
Ces commandes confirment les services définis dans votre fichier compose et si Docker voit les volumes que vous attendez.
Persistance des données avant tout changement
L'une des erreurs de configuration les plus courantes est la perte de données lors de la recréation d'un conteneur. Avant de poursuivre le débogage, confirmez où vivent les données persistantes :
- Volume nommé : géré par Docker, facile à réutiliser. Exemple d'extrait Compose :
services:
app:
image: myapp:1.2
volumes:
- app_data:/var/lib/app
volumes:
app_data:
- Montage bind : mappe directement un chemin de l'hôte. Utile pour le développement mais peut échouer en production si le chemin hôte n'existe pas sur toutes les machines.
Effectuez un test de redémarrage dans un environnement de staging :
- Arrêtez le conteneur :
docker stop web-app - Supprimez-le :
docker rm web-app - Recréez-le à partir du même fichier compose ou de la même commande run.
- Vérifiez si les données sont toujours présentes :
docker exec web-app ls /var/lib/app
Si les fichiers manquent, l'application écrivait probablement dans la couche inscriptible du conteneur au lieu d'un volume ou d'un montage. Corrigez le Dockerfile ou le fichier compose pour monter un volume à ce chemin.
Chemin de configuration sûr
Lorsque vous avez identifié une erreur de configuration probable, suivez un processus de changement chirurgical. Ne modifiez jamais les fichiers à l'intérieur d'un conteneur en cours d'exécution avec docker exec vi — ce changement disparaît à la recréation du conteneur et n'est pas suivi.
Utilisez correctement les variables d'environnement
Une erreur fréquente est de passer des secrets via des variables d'environnement en clair dans une commande docker run ou un fichier compose, où ils sont visibles dans docker inspect et les listes de processus.
Mauvaise pratique :
docker run -e DATABASE_PASSWORD=supersecret myapp
docker inspect affichera ce mot de passe en clair.
Mieux : utilisez les secrets Docker (pour Swarm) ou un fichier de secrets monté en volume en lecture seule. Exemple de fichier compose avec montage d'un fichier de secret :
services:
app:
image: myapp:1.2
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
À l'intérieur du conteneur, le secret apparaît à /run/secrets/db_password. L'application lit à partir de ce chemin.
Changez une variable à la fois
Si vous soupçonnez qu'une variable d'environnement est incorrecte, modifiez uniquement cette variable et redémarrez le conteneur. Ne regroupez pas de modifications sans rapport.
Pour un conteneur unique :
docker stop web-app
docker rm web-app
docker run -d --name web-app \
-e APP_ENV=production \
-e DATABASE_URL=postgres://db:5432/app \
-p 8080:80 \
myapp:1.2
Ensuite, vérifiez les journaux et la santé :
docker logs web-app --tail 50
docker inspect web-app --format '{{.State.Health.Status}}'
Si l'état de santé n'est pas « healthy » en une minute, vous savez que la nouvelle variable d'environnement a contribué au problème.
Pour Compose, modifiez docker-compose.yml, puis recréez uniquement le service affecté :
docker compose up -d --force-recreate app
--force-recreate garantit que le conteneur est reconstruit avec la nouvelle configuration au lieu de réutiliser l'ancienne.
Plan de retour en arrière
Avant d'appliquer un changement, écrivez la commande exacte de retour en arrière dans vos notes. Pour un service Compose, la configuration précédente est dans votre contrôle de version — il suffit de récupérer le commit précédent et de relancer docker compose up -d. Pour un docker run manuel, vous pouvez inspecter la configuration actuelle du conteneur avant suppression avec :
docker inspect web-app --format '{{json .Config}}' > web-app-config-backup.json
Ensuite, si nécessaire, reconstruisez la commande run à partir de ce JSON. Mieux encore, conservez toutes les définitions de conteneurs dans un fichier compose ou un script sous contrôle de version afin que le retour en arrière soit fiable.
Vérification et diagnostics
Après tout changement, vérifiez que le conteneur se comporte comme prévu. Ne vous fiez pas à « il a démarré » — vérifiez la fonctionnalité réelle et l'utilisation des ressources.
Vérifiez les journaux pour les motifs d'erreur
docker logs web-app --tail 200
Cherchez des exceptions récurrentes, des messages de connexion refusée ou des erreurs de configuration invalide. Pour un conteneur qui plante immédiatement après le démarrage, utilisez :
docker logs web-app
Si le conteneur a redémarré plusieurs fois, ajoutez des horodatages :
docker logs --timestamps web-app
Pour un conteneur qui se termine immédiatement, exécutez-le au premier plan avec un shell de substitution pour déboguer les problèmes de démarrage :
docker run --rm -it myapp:1.2 /bin/sh
Ensuite, exécutez manuellement la commande de démarrage de l'application pour voir l'erreur.
Testez la connectivité réseau
Depuis l'intérieur d'un conteneur en cours d'exécution, testez la résolution DNS et la connectivité TCP :
docker exec web-app ping -c 3 database
Sortie attendue si le DNS fonctionne :
PING database (172.18.0.2) 56(84) bytes of data.
64 bytes from database (172.18.0.2): icmp_seq=1 ttl=64 time=0.089 ms
Si le nom ne se résout pas, le conteneur n'est peut-être pas sur le même réseau défini par l'utilisateur. Vérifiez les réseaux :
docker network ls
docker network inspect bridge
Pour un conteneur attaché à un réseau spécifique, assurez-vous que les services Compose partagent ce réseau.
Vérifiez les conflits de liaison de port :
docker ps --format "table {{.Names}}\t{{.Ports}}"
Si un autre conteneur utilise déjà le port hôte que vous voulez, vous verrez une erreur de liaison de port dans les journaux.
Contrôles de santé et disponibilité
Un contrôle de santé mal configuré peut marquer un conteneur sain comme malsain, déclenchant des redémarrages inutiles. Inspectez la définition du contrôle de santé :
docker inspect web-app --format '{{json .Config.Healthcheck}}' | jq
Exemple de sortie :
{
"Test": ["CMD-SHELL", "curl -f http://localhost/ || exit 1"],
"Interval": 30000000000,
"Timeout": 5000000000,
"Retries": 3,
"StartPeriod": 60000000000
}
Les intervalles sont en nanosecondes. Une période de démarrage de 60 secondes (60 000 000 000 ns) peut être trop courte si votre application met plus de temps à s'initialiser. Ajustez dans votre Dockerfile ou fichier compose :
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 30s
timeout: 5s
retries: 3
start_period: 120s
Après la mise à jour, recréez le conteneur et surveillez l'état de santé au fil du temps :
watch -n 5 'docker inspect web-app --format "{{.State.Health.Status}}"'
Modes de défaillance et récupération
Examinons des erreurs de configuration spécifiques et comment récupérer de chacune.
Erreur 1 : Montage de volume manquant entraînant une perte de données
Symptôme : Après la recréation du conteneur, tous les fichiers téléchargés par les utilisateurs ont disparu. Pourquoi cela arrive : L'application a écrit dans un répertoire à l'intérieur du système de fichiers du conteneur, pas dans un volume monté. Récupération : Vérifiez le système de fichiers de l'ancien conteneur (s'il est encore disponible) avec docker cp pour récupérer les fichiers avant de le supprimer. Ensuite, mettez à jour votre fichier compose pour ajouter un volume nommé au chemin de données de l'application. Recréez le conteneur et vérifiez la persistance avec un test de redémarrage.
Erreur 2 : Fautes de frappe dans les variables d'environnement causant des échecs de connexion
Symptôme : L'application ne peut pas se connecter à la base de données, mais le conteneur de base de données est en cours d'exécution. Pourquoi cela arrive : Une faute de frappe dans le nom de la variable d'environnement, par exemple, DATABSE_URL au lieu de DATABASE_URL. Récupération : Inspectez l'environnement du conteneur :
docker exec web-app env | sort
Comparez avec ce que l'application attend. Corrigez le fichier compose ou le Dockerfile, puis recréez le conteneur. Utilisez une bibliothèque de configuration qui valide les variables d'environnement requises au démarrage pour échouer rapidement.
Erreur 3 : Mappage de port incorrect empêchant l'accès externe
Symptôme : Le service fonctionne mais ne peut pas être atteint depuis l'hôte. Pourquoi cela arrive : Soit le mauvais port du conteneur est mappé, soit le port hôte est déjà utilisé. Récupération : Vérifiez les mappages de port réels :
docker port web-app
Comparez avec le port d'écoute de l'application (vérifiez le EXPOSE du Dockerfile ou les journaux de démarrage). Mettez à jour le mappage de port et recréez. Vérifiez avec curl http://localhost:8080.
Erreur 4 : Limites de ressources non définies, le conteneur tue des processus
Symptôme : Le conteneur est tué avec le code de sortie 137 (SIGKILL) sous charge. Pourquoi cela arrive : Le conteneur a dépassé la limite de mémoire (si définie) ou l'hôte a manqué de mémoire. Récupération : Inspectez l'utilisation actuelle des ressources :
docker stats --no-stream
Définissez des limites de mémoire et de CPU appropriées dans votre fichier compose :
services:
app:
image: myapp:1.2
deploy:
resources:
limits:
cpus: '0.50'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
Recréez et exécutez des tests de charge pour confirmer la stabilité.
Erreur 5 : Utilisation d'une mauvaise image de base ou balise
Symptôme : L'application se comporte différemment en production par rapport au développement, ou des dépendances manquent. Pourquoi cela arrive : Le Dockerfile utilise une balise mutable comme latest, ou l'image de base diffère entre les environnements. Récupération : Épinglez l'image de base à un digest ou une balise de version spécifique dans votre Dockerfile. Reconstruisez l'image, poussez vers le registre et redéployez. Utilisez docker image inspect pour vérifier que l'ID de l'image correspond entre les environnements.
Pièges courants et comment les éviter
Au-delà des erreurs spécifiques ci-dessus, voici des pièges généraux qui font trébucher les équipes.
Modifier des fichiers à l'intérieur du conteneur
Exécuter docker exec -it web-app bash et modifier un fichier de configuration avec vi ou sed est tentant pour une solution rapide. Mais le changement est perdu à la recréation du conteneur et n'est pas visible pour les autres membres de l'équipe. À la place, montez un fichier de configuration ou utilisez des variables d'environnement, puis recréez.
Ignorer les journaux jusqu'à ce que quelque chose casse
Les journaux sont le premier endroit à regarder, mais de nombreuses équipes ne les collectent pas de manière centralisée. Utilisez les pilotes de journalisation Docker pour transférer les journaux vers un service d'agrégation. Exemple d'extrait compose :
services:
app:
image: myapp:1.2
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Pour un serveur syslog central, passez le pilote à syslog et spécifiez l'adresse. Cela garantit que les journaux sont disponibles même si le conteneur est supprimé.
Ne pas utiliser de fichier .dockerignore
Sans fichier .dockerignore, le contexte de construction peut inclure le répertoire local .git, node_modules ou des secrets, rendant l'image inutilement volumineuse et pouvant fuiter des données sensibles. Créez un fichier .dockerignore à la racine de votre contexte de construction :
.git
node_modules
*.log
.env
secrets
Utilisation excessive de la balise latest
Comme mentionné, latest rend les constructions non reproductibles. Utilisez toujours des balises de version sémantique ou des digests dans les fichiers de déploiement en production. Pour le développement local, latest est acceptable mais évitez-le dans les pipelines CI/CD.
Omettre les contrôles de santé
Sans contrôle de santé, Docker ne peut pas savoir si votre application est vraiment prête. Définissez un contrôle de santé qui teste un point de terminaison critique, pas seulement que le processus est en cours d'exécution. Utilisez start_period pour laisser suffisamment de temps aux applications à démarrage lent.
Liste de contrôle des opérations
Utilisez cette liste de contrôle avant et après tout changement de configuration d'un service conteneurisé. Attribuez chaque élément à une seule personne responsable (pas une équipe) et révisez la liste lors de chaque publication ou revue d'incident.
| Étape | Action | Responsable | Fréquence |
|---|---|---|---|
| 1 | Enregistrer l'état actuel : sortie de docker ps, docker logs, docker inspect | Ingénieur d'astreinte | Au début de l'incident |
| 2 | Identifier le changement nécessaire et son rayon d'impact | Propriétaire de l'application (par ex., Priya Shah, responsable ingénierie) | Pendant la planification du changement |
| 3 | Sauvegarder les données critiques : docker cp <conteneur>:/chemin /sauvegarde/ si nécessaire | Administrateur de base de données | Avant un changement destructif |
| 4 | Appliquer le plus petit changement possible (une variable d'environnement, un montage, un port) | Ingénieur d'astreinte | Au moment du changement |
| 5 | Vérifier la fonctionnalité : journaux, état de santé et réponse réelle du point de terminaison | Ingénieur QA | Immédiatement après le changement |
| 6 | Documenter le changement et la procédure de retour en arrière dans le runbook | Propriétaire du service | Dans les 24 heures |
| 7 | Exécuter un test de redémarrage en staging pour confirmer la persistance des données et la configuration | Propriétaire de l'application | Avant le prochain déploiement |
| 8 | Revoir la chronologie de l'incident et mettre à jour le runbook si nécessaire | Responsable ingénierie | Revue hebdomadaire des incidents |
Cette liste de contrôle garantit qu'aucune étape n'est sautée sous pression. Les responsables sont illustratifs — remplacez-les par les noms et rôles réels de votre organisation.
Conclusion
Déboguer les erreurs de configuration des conteneurs Docker est un processus discipliné, pas un jeu de devinettes. Commencez par un inventaire complet de l'environnement, comprenez l'état actuel, faites un changement minimal à la fois et vérifiez avec des commandes concrètes et des sorties attendues. Ayez toujours un plan de retour en arrière avant de changer quoi que ce soit.
Les flux de travail les plus fiables rendent les défaillances visibles grâce aux journaux et aux contrôles de santé, protègent les valeurs sensibles avec la gestion des secrets, limitent les changements à la ressource prévue et définissent la vérification de la récupération avant qu'un incident ne force une décision. Appliquez la liste de contrôle et les pièges de ce guide pour construire des déploiements de conteneurs plus sûrs et plus résilients. Comme prochaine étape, choisissez un service que vous gérez, parcourez la liste de contrôle des opérations et documentez au moins une procédure de récupération dans le runbook de votre équipe.
Un flux de travail technique fiable rend les défaillances visibles, protège les valeurs sensibles, limite les changements à la ressource prévue et définit la vérification de la récupération avant que l'incident ne force la décision.