Introduction
Docker Compose simplifie la gestion d'applications multi-conteneurs, mais sa facilité d'utilisation masque des pièges de configuration qui peuvent entraîner des pertes de données, des interruptions de service ou des vulnérabilités de sécurité. Cet article aborde les erreurs courantes de configuration Docker Compose, en proposant des exemples pratiques et des solutions concrètes pour les développeurs, les ingénieurs DevOps et les équipes techniques de startups.
Nous nous concentrons sur des étapes observables et vérifiables : vérifier l'état actuel avant d'apporter des modifications, utiliser des valeurs par défaut raisonnables, éviter les secrets codés en dur et savoir comment revenir en arrière. Chaque section comprend des extraits de commandes concrets, les sorties attendues et les procédures de récupération.
Inventaire des versions et de l'environnement
Avant de modifier toute configuration Compose, comprenez votre environnement. Différentes versions de Docker Compose ont des capacités et une syntaxe différentes. Exécutez :
docker compose version
Exemple de sortie :
Docker Compose version v2.27.1
Pour Compose v1 (basé sur Python), utilisez docker-compose version. Notez la différence : la v2 est écrite en Go et est un plugin pour la CLI Docker, tandis que la v1 est un binaire autonome. De nombreux exemples en ligne font référence à la v1 ; si vous utilisez la v2, les commandes peuvent nécessiter docker compose (avec un espace) au lieu de docker-compose.
Vérifiez la version du moteur Docker :
docker version --format '{{.Server.Version}}'
Celle-ci doit être compatible avec votre version de Compose.
Inventoriez les conteneurs en cours d'exécution et leur état :
docker compose ps
Cela affiche les services du projet, leur état et les ports exposés. Pour une inspection plus approfondie, utilisez docker inspect sur un conteneur spécifique pour voir les montages, les réseaux et les variables d'environnement (bien que les variables d'environnement puissent être sensibles à moins d'être masquées).
Séparer l'observation de l'intervention
Prenez l'habitude d'enregistrer l'état actuel avant de modifier quoi que ce soit. Par exemple, sauvegardez la configuration actuelle :
docker compose config > compose-backup-$(date +%Y%m%d).yml
Cela vous permet de comparer avec la nouvelle configuration et de revenir en arrière si nécessaire.
Vérification de l'emplacement de stockage des données
Une erreur fréquente est de supposer que les données persistent lors des recréations de conteneurs sans configurer correctement les volumes. Dans Compose, vous pouvez monter un volume nommé ou un montage lié (bind mount). Un volume nommé est géré par Docker et généralement stocké sous /var/lib/docker/volumes/, tandis qu'un montage lié mappe directement un chemin hôte.
Exemple d'extrait Compose avec un volume nommé :
services:
db:
image: postgres:16
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
Avec un montage lié :
volumes:
- ./data:/var/lib/postgresql/data
Pour vérifier la persistance des données, effectuez un test de redémarrage : arrêtez et supprimez le conteneur, puis relancez-le :
docker compose down
docker compose up -d
Vérifiez ensuite si les données sont toujours accessibles. Si les données disparaissent, le service a probablement écrit dans le système de fichiers éphémère du conteneur au lieu du volume prévu.
Chemin de configuration sûr
Lorsque vous modifiez des fichiers Compose, suivez une approche systématique pour éviter d'introduire des erreurs.
Valider la configuration avant de l'appliquer
Validez toujours votre fichier Compose avec :
docker compose config
Cela vérifie la syntaxe et résout les variables, affichant la configuration finale. En cas d'erreurs, la commande se termine avec un code non nul et affiche des messages. Par exemple, si vous avez une indentation YAML invalide :
docker compose config
Sortie :
services.db.environment.MYSQL_ROOT_PASSWORD contient un type invalide, il devrait être une chaîne ou un objet
Remarque : docker compose config ne garantit pas que les services fonctionneront correctement ; il vérifie uniquement la syntaxe et le schéma.
Utiliser correctement les variables d'environnement
Au lieu de coder en dur les secrets dans le fichier Compose, utilisez la substitution de variables d'environnement. Dans docker-compose.yml :
services:
app:
image: myapp:latest
environment:
DB_PASSWORD: ${DB_PASSWORD}
Spécifiez ensuite la valeur dans un fichier .env dans le même répertoire (non versionné) :
DB_PASSWORD=supersecret
Ou définissez-la dans le shell :
export DB_PASSWORD=supersecret
Lorsque vous exécutez docker compose config, la variable sera substituée. Pour éviter d'exposer les secrets, utilisez docker compose config --no-interpolate pour voir le fichier brut sans substitution.
Une erreur courante est d'utiliser $$ (double dollar) pour échapper la substitution de variable lorsque vous souhaitez passer la chaîne littérale. Par exemple, si vous voulez passer un $DB_PASSWORD littéral au conteneur, écrivez $$DB_PASSWORD dans le fichier Compose.
Limiter la portée : utiliser les profils et les fichiers de surcharge
Utilisez les profils Compose pour démarrer optionnellement des services. Par exemple :
services:
app:
image: myapp:latest
debug-tools:
image: busybox
profiles: ["debug"]
Avec les profils, docker compose up démarre uniquement app, tandis que docker compose --profile debug up démarre les deux. Cela empêche le démarrage accidentel de services de débogage en production.
Une autre technique consiste à utiliser plusieurs fichiers Compose, comme compose.yaml et compose.override.yaml. Le fichier de surcharge peut spécifier des paramètres spécifiques au développement. Par défaut, si les deux fichiers existent, Compose les fusionne automatiquement ; vous pouvez afficher la configuration fusionnée avec :
docker compose config
Si vous utilisez des noms de fichiers personnalisés, spécifiez-les avec les options -f. Cela vous permet de garder la configuration de base stable et d'ajouter des modifications locales sans toucher au fichier principal.
Apporter des modifications petites et réversibles
Modifiez un paramètre à la fois et testez immédiatement. Par exemple, si vous devez augmenter la limite de mémoire d'un service, modifiez le fichier Compose :
services:
app:
deploy:
resources:
limits:
memory: 512M
Appliquez :
docker compose up -d
Vérifiez ensuite si le service est sain et si l'utilisation de la mémoire est dans les limites. Si des problèmes surviennent, annulez facilement la modification en défaisant la modification et en exécutant à nouveau docker compose up -d.
Vérification et diagnostics
Après avoir appliqué les modifications, vérifiez que le système se comporte comme prévu.
Vérifier l'état des services
Utilisez docker compose ps pour voir si les conteneurs sont en cours d'exécution :
docker compose ps
Exemple de sortie :
NAME COMMAND SERVICE STATUS PORTS
myapp_app_1 "python app.py" app running 0.0.0.0:8000->8000/tcp
myapp_db_1 "docker-entrypoint.s…" db running 5432/tcp
Si un conteneur est exited ou restarting, examinez les journaux.
Lire efficacement les journaux
Utilisez docker compose logs pour afficher les journaux :
docker compose logs app
Suivez les journaux en temps réel :
docker compose logs -f app
Si vous avez besoin de voir uniquement les lignes récentes :
docker compose logs --tail=100 app
Les indicateurs d'échec courants dans les journaux incluent les exceptions, les erreurs de connexion refusée ou les messages de boucle de crash. Par exemple, si la base de données est injoignable, vous pourriez voir :
ERROR: could not connect to database: connection refused
Inspecter les détails du conteneur
Utilisez docker inspect pour obtenir des informations détaillées sur un conteneur, y compris son état de santé, ses montages et son environnement :
docker inspect myapp_app_1
Pour extraire des champs spécifiques :
docker inspect --format '{{.State.Health.Status}}' myapp_app_1
Si l'état de santé est unhealthy, examinez la configuration du healthcheck.
Définir des healthchecks
Ajoutez des healthchecks à vos services pour détecter automatiquement les défaillances. Exemple pour une application web :
services:
web:
image: nginx:alpine
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Avec les healthchecks, docker compose ps affiche l'état de santé, et vous pouvez utiliser docker compose up --wait pour bloquer jusqu'à ce que les services soient sains.
Déboguer à l'intérieur du conteneur
Utilisez docker compose exec pour exécuter des commandes dans un conteneur en cours d'exécution :
docker compose exec app sh
Cela vous donne un shell dans le conteneur pour vérifier les fichiers, les processus ou tester la connectivité. Pour des commandes ponctuelles :
docker compose exec app env
Remarque : exec nécessite que le conteneur soit en cours d'exécution ; si le conteneur se termine immédiatement, vous devrez peut-être remplacer le point d'entrée :
docker compose run --entrypoint sh app
Modes de défaillance et récupération
Même avec une planification minutieuse, des problèmes surviennent. Voici des scénarios de défaillance typiques et comment récupérer.
Le conteneur se termine immédiatement
Si un conteneur démarre et se termine avec le code 0 ou non nul, vérifiez les journaux :
docker compose logs <service>
Souvent, la commande est mal configurée. Dans Compose, l'option command remplace le CMD par défaut. Par exemple, si vous avez :
services:
app:
image: alpine
command: echo "hello"
Le conteneur exécutera echo et se terminera. Pour le maintenir en vie, vous pourriez avoir besoin d'un processus de longue durée. Récupération : ajustez la command pour un processus de premier plan.
Conflits de ports
Si un service ne parvient pas à se lier à un port, vous verrez une erreur comme :
Error starting userland proxy: listen tcp4 0.0.0.0:80: bind: address already in use
Identifiez quel processus utilise le port sur l'hôte :
sudo lsof -i :80
Ou sur Linux :
sudo ss -tulpn | grep :80
Ensuite, arrêtez le processus en conflit ou modifiez le port hôte dans le fichier Compose :
ports:
- "8080:80"
Erreurs de montage de volume
Si un chemin de montage lié n'existe pas sur l'hôte, Docker peut le créer en tant que répertoire avec la propriété root, ce qui entraîne des problèmes de permissions à l'intérieur du conteneur. Par exemple :
volumes:
- ./logs:/app/logs
Si ./logs n'existe pas, Docker le crée, mais l'utilisateur du conteneur peut ne pas avoir les permissions d'écriture. Récupération : créez le répertoire avec des permissions appropriées avant de démarrer :
mkdir -p logs && chmod 777 logs
Ou ajustez l'utilisateur du conteneur pour qu'il corresponde.
Variable d'environnement non substituée
Si vous référencez une variable dans le fichier Compose mais qu'elle n'est pas définie, docker compose config émettra un avertissement ou une erreur. Par exemple :
environment:
- DATABASE_URL=${DATABASE_URL}
Si DATABASE_URL n'est pas définie, le conteneur reçoit une chaîne vide. Pour éviter cela, définissez une valeur par défaut dans le fichier Compose :
environment:
- DATABASE_URL=${DATABASE_URL:-postgres://localhost:5432}
Vérifiez ensuite avec docker compose config.
Stratégie de récupération : retour en arrière
Soyez toujours en mesure de revenir à un état connu et fonctionnel. Cela signifie :
- Gardez un contrôle de version pour vos fichiers Compose (par exemple, Git).
- Avant d'apporter des modifications, créez une sauvegarde de la configuration actuelle et, le cas échéant, des données.
- Utilisez
docker compose configpour sauvegarder la configuration entièrement résolue pour référence future. - Si une modification casse l'application, récupérez la version précédente et appliquez-la :
git checkout -- docker-compose.ymlpuisdocker compose up -d.
Pour les données, si vous utilisez des volumes nommés, envisagez des instantanés ou des sauvegardes. Par exemple, pour sauvegarder une base de données PostgreSQL dans un volume, vous pourriez exécuter :
docker compose exec db pg_dump -U postgres mydb > backup.sql
Puis pour restaurer :
cat backup.sql | docker compose exec -T db psql -U postgres mydb
Pièges courants et comment les éviter
Cette section regroupe les erreurs fréquentes, pourquoi elles se produisent et comment les prévenir ou les récupérer.
Piège 1 : Utiliser le tag latest pour les images
Utiliser image: myapp:latest peut entraîner un comportement imprévisible car latest peut changer de manière inattendue. Cela rend le retour en arrière difficile et nuit à la reproductibilité.
Comment éviter : Épinglez des versions d'image spécifiques, par exemple image: myapp:1.2.3. Utilisez un digest pour une immuabilité encore plus forte : myapp@sha256:....
Piège 2 : Coder en dur les secrets dans les fichiers Compose
Stocker des mots de passe ou des clés API directement dans docker-compose.yml est un risque de sécurité, surtout si le fichier est versionné dans un dépôt.
Comment éviter : Utilisez des variables d'environnement ou des secrets Docker. Pour le développement local, utilisez un fichier .env qui est gitignoré. Pour la production, utilisez un gestionnaire de secrets.
Piège 3 : Mal comprendre les types de volumes
Utiliser des montages liés quand vous vouliez des volumes nommés (ou vice versa) peut entraîner une perte de données ou des problèmes de permissions. Les montages liés sont spécifiques à l'hôte ; les volumes nommés sont portables et gérés par Docker.
Comment éviter : Définissez clairement le type et le but du volume. Utilisez des volumes nommés pour les données d'application persistantes, des montages liés pour le développement où vous avez besoin de la synchronisation du code en direct.
Piège 4 : Ne pas définir de politiques de redémarrage
Par défaut, les conteneurs ne redémarrent pas automatiquement sauf si vous spécifiez une politique de redémarrage. Si un conteneur plante, il reste arrêté.
Comment éviter : Définissez restart: unless-stopped ou restart: always pour les services de production. Exemple :
services:
app:
restart: unless-stopped
Piège 5 : Ignorer les limites de ressources
Sans limites, un conteneur peut consommer toutes les ressources de l'hôte, affectant les autres services.
Comment éviter : Définissez des limites de mémoire et de CPU en utilisant la clé deploy (pour Swarm) ou mem_limit, cpus (pour non-Swarm). Exemple :
services:
app:
mem_limit: 512m
cpus: 0.5
Piège 6 : Mal configurer les réseaux
Les services peuvent ne pas communiquer parce qu'ils sont sur des réseaux différents ou que les alias de réseau sont incorrects.
Comment éviter : Définissez explicitement les réseaux dans le fichier Compose et attachez les services aux réseaux appropriés. Utilisez docker compose exec pour tester la connectivité avec ping ou curl.
Piège 7 : Ne pas sauvegarder les données
Les données dans les conteneurs ou les volumes peuvent être perdues si vous supprimez des volumes par inadvertance.
Comment éviter : Sauvegardez régulièrement les volumes en utilisant docker run --rm -v volume_name:/data -v $(pwd):/backup alpine tar czf /backup/backup.tar.gz -C /data .
Piège 8 : Négliger les limitations de depends_on
depends_on contrôle uniquement l'ordre de démarrage ; il n'attend pas que la dépendance soit prête (par exemple, que la base de données accepte les connexions).
Comment éviter : Utilisez des healthchecks et depends_on avec condition: service_healthy (Compose v2.1+). Exemple :
services:
web:
depends_on:
db:
condition: service_healthy
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
Liste de contrôle opérationnelle
Utilisez cette liste avant et après avoir apporté des modifications de configuration Compose. Attribuez un responsable (par exemple, ingénieur DevOps) et une fréquence de révision.
| Étape | Action | Commande/Détail | Responsable | Révision |
|---|---|---|---|---|
| 1 | Enregistrer la version actuelle de Compose | docker compose version | Responsable DevOps | À chaque modification |
| 2 | Sauvegarder la configuration actuelle | docker compose config > backup.yml | Responsable DevOps | À chaque modification |
| 3 | Valider la nouvelle configuration | docker compose config | Développeur | À chaque modification |
| 4 | Appliquer en mode simulation (si disponible) | docker compose up --no-start | Développeur | À chaque modification |
| 5 | Appliquer les modifications | docker compose up -d | Responsable DevOps | À chaque modification |
| 6 | Vérifier l'état des services | docker compose ps | Développeur | Immédiatement |
| 7 | Vérifier les journaux pour les erreurs | docker compose logs --tail=50 | Développeur | Immédiatement |
| 8 | Tester la fonctionnalité de l'application | Tests manuels ou automatisés | Ingénieur QA | Après chaque déploiement |
| 9 | Vérifier la persistance des données | docker compose down && docker compose up -d puis vérifier les données | Responsable DevOps | Mensuellement |
| 10 | Examiner la sécurité (secrets, permissions) | docker compose config --no-interpolate pour vérifier les secrets | Agent de sécurité | Trimestriellement |
Chaque élément doit avoir un responsable nommé. Par exemple, le responsable DevOps (par exemple, « Priya Shah, responsable de l'ingénierie ») est responsable des sauvegardes de configuration et de l'application des modifications. Les intervalles de révision peuvent être ajustés en fonction du risque, mais les paramètres critiques doivent être examinés après chaque modification.
Conclusion
Les erreurs de configuration Docker Compose peuvent entraîner des temps d'arrêt et des pertes de données, mais elles sont évitables avec des pratiques systématiques. En validant les configurations, en gérant correctement les volumes et les variables d'environnement, et en ayant un plan de retour en arrière, vous pouvez réduire considérablement les risques.
Observez toujours avant de changer, apportez des modifications petites et réversibles, et vérifiez le résultat avec des commandes concrètes. Tenez une liste de contrôle et attribuez des responsabilités pour garantir la responsabilisation. Pour approfondir, explorez la documentation officielle de Docker sur la référence des fichiers Compose, les healthchecks et la gestion des secrets.
Prochaine étape : choisissez une amélioration à faible risque de cet article, comme l'ajout de healthchecks à un service, et implémentez-la dans un environnement de développement. Enregistrez l'état actuel, appliquez la modification et vérifiez le comportement attendu. Documentez tout problème pour référence future.
En adoptant ces pratiques, vous construirez un environnement conteneurisé plus résilient.