Docker simplifie le déploiement d'applications, mais les mauvaises configurations peuvent compromettre ses avantages. Des paramètres par défaut non sécurisés à une utilisation inefficace des ressources, les erreurs courantes provoquent des temps d'arrêt, des failles de sécurité et une dégradation des performances. Ce guide propose une approche pratique, étape par étape, pour identifier, corriger et éviter ces pièges à l'aide d'exemples concrets et de techniques de validation applicables immédiatement.
Inventaire des versions et de l'environnement
Avant d'apporter des modifications, établissez une base de référence claire de votre configuration actuelle. Omettre cette étape entraîne des changements qui cassent des dépendances ou introduisent des incompatibilités de versions.
Capturez les éléments essentiels :
- Versions de Docker Engine et Compose : Exécutez
docker versionetdocker compose version. La disponibilité des fonctionnalités (commedeploy.resourcesdans les fichiers Compose ou BuildKit) dépend de ces versions. - Conteneurs et images en cours d'exécution : Utilisez
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"etdocker imagespour cartographier la topologie de votre application. - Sources de configuration : Faites l'inventaire des variables d'environnement, fichiers de configuration, secrets Docker et montages bind. Notez quels services tirent leur configuration de fichiers
.envversus des secrets au niveau de l'orchestration.
Documentez les résultats dans un tableau de référence que vous pourrez mettre à jour pendant la remédiation :
| Composant | Version / Valeur | Notes |
|---|---|---|
| Docker Engine | 24.0.7 | Dernière version LTS ; supporte le plugin docker compose nativement |
| Docker Compose | v2.24.0 | Plugin inclus avec Docker Desktop ; binaire autonome disponible |
| Image de base (service API) | python:3.11-slim-bookworm | Base Debian 12 ; surface d'attaque plus réduite que l'image complète |
| Image de base (Worker) | node:20-alpine | libc Musl ; vérifier que les dépendances natives compilent correctement |
| Orchestration | Docker Compose (local), Swarm (staging) | Les fichiers Compose doivent être compatibles avec les deux |
Conservez cet inventaire dans le contrôle de version aux côtés de vos fichiers docker-compose.yml afin que les futurs audits démarrent d'un état connu.
Chemin de configuration sûr : changements incrémentaux avec validation
Les changements de configuration à grande échelle appliqués simultanément rendent l'analyse des causes racines impossible. Adoptez un flux de travail étroit-à-large : modifiez un service, validez-le minutieusement, puis propagez.
Définir le périmètre par itération
Choisissez un seul domaine de configuration par cycle. Les zones à fort impact typiques incluent :
- Contraintes de ressources :
--memory,--cpus,--memory-swap(prévenir les problèmes de voisin bruyant) - Durcissement de la sécurité :
--cap-drop=ALL,--cap-addseulement le nécessaire,--read-only,--security-opt=no-new-privileges:true - Stratégie de stockage : Volumes nommés vs montages bind,
tmpfspour données éphémères, options du drivervolume - Posture réseau : Réseaux internes uniquement, mappage de ports publiés (
127.0.0.1:8080:80vs0.0.0.0:8080:80), options DNS
Implémentation étape par étape pour un seul service
- Sauvegarder l'état actuel :
cp docker-compose.yml docker-compose.yml.bak-$(date +%F) - Modifier un service dans
docker-compose.yml(ou le fichier d'override approprié). - Valider la syntaxe et l'interpolation :
docker compose config --servicesliste les services ;docker compose configaffiche la configuration entièrement résolue — détectant les erreurs d'expansion de variables tôt. - Déployer le service unique :
docker compose up -d --no-deps <nom-service> - Vérifier la santé :
docker compose ps <nom-service>,docker compose logs -f <nom-service>, etdocker stats --no-stream <nom-conteneur>
Exemple concret : application de limites mémoire à une API Python fuyante
Un service FastAPI (api) consomme progressivement de la mémoire à cause d'un cache non libéré. L'hôte dispose de 4 Go RAM ; trois services la partagent.
Avant (sans limites) :
services:
api:
image: myorg/api:1.4.0
ports:
- "8000:8000"
Après (avec limite et réservation) :
services:
api:
image: myorg/api:1.4.0
ports:
- "127.0.0.1:8000:8000" # liaison à localhost uniquement
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
# Optionnel : politique de redémarrage pour les OOM kills
deploy:
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
Valider : docker compose config affiche le bloc deploy.resources résolu. Déployer : docker compose up -d --no-deps api Observer : docker stats api pendant 10 minutes sous charge ; confirmer que le RSS reste sous 512 Mo. Si le conteneur est tué par OOM, les logs montrent Exit 137 — augmenter la limite ou corriger la fuite.
Vérification et diagnostics : prouver que le changement fonctionne
Les suppositions sur le comportement de la configuration sont une source principale d'incidents en production. Vérifiez chaque changement avec des preuves concrètes.
Commandes d'inspection principales
| Commande | Objectif | Sections clés à vérifier |
|---|---|---|
docker inspect <conteneur> | Configuration complète à l'exécution | HostConfig.Memory, HostConfig.CapDrop, Mounts, NetworkSettings |
docker compose config | Configuration Compose rendue | Valide la substitution de variables, la logique de fusion, extends |
docker exec <conteneur> cat /proc/self/status | Vue interne des limites | VmRSS, VmPeak, CapEff (capacités effectives) |
docker diff <conteneur> | Changements du système de fichiers depuis le démarrage | Détecte les écritures inattendues sur les couches en lecture seule |
docker stats --no-stream <conteneur> | Instantané de l'utilisation des ressources | CPU %, MEM %, NET I/O, BLOCK I/O, PIDs |
Tableau de validation attendu vs réel
Avant le déploiement, notez des attentes mesurables. Après le déploiement, enregistrez les résultats réels.
| Vérification | Attendu | Résultat réel | Statut |
|---|---|---|---|
| Limite mémoire appliquée | RSS du conteneur ≤ 512 Mo sous charge | 498 Mo en pointe | Pass |
| Système de fichiers racine en lecture seule | touch /test échoue avec Read-only file system | Erreur loguée, application inchangée | Pass |
| Capacités retirées | CAP_NET_BIND_SERVICE seulement ; CAP_SYS_ADMIN absent | CapEff: 00000000a80425fb (seulement NET_BIND, CHOWN, DAC_OVERRIDE, FOWNER, FSETID, KILL, SETGID, SETUID) | Pass |
| Portée de liaison du port | Écoute sur 127.0.0.1:8000 seulement | ss -ltnp montre 127.0.0.1:8000 | Pass |
| Sortie des logs | Logs JSON structurés vers stdout | Lignes JSON présentes dans docker compose logs api | Pass |
Si une vérification échoue, considérez-la comme un blocage — ne promouvez pas le changement vers d'autres environnements tant qu'il n'est pas résolu.
Modes de défaillance et récupération : planifier l'inévitable
Même les changements bien testés rencontrent des problèmes spécifiques à l'environnement. Anticipez les schémas d'échec les plus courants et codifiez les étapes de récupération.
Scénarios d'échec fréquents
| Mode de défaillance | Symptômes | Commandes de diagnostic | Cause racine typique |
|---|---|---|---|
| Conflit de port | Error starting userland proxy: listen tcp4 0.0.0.0:8080: bind: address already in use | docker compose ps, ss -ltnp | grep 8080 | Un autre service/processus occupe le port ; collision de mappage de port hôte |
| Boucle OOM kill | Le conteneur redémarre en boucle ; docker inspect montre OOMKilled: true | docker stats, docker compose logs, dmesg | grep -i oom | Limite trop basse pour la charge réelle ; fuite de mémoire |
| Permission volume refusée | L'application logue PermissionError: [Errno 13] Permission denied: '/data' | docker inspect --format '{{.Mounts}}' <conteneur>, ls -la /chemin/hote | Désaccord UID/GID entre utilisateur conteneur et répertoire hôte ; SELinux/AppArmor |
| Erreur de syntaxe config | docker compose up échoue avec yaml: line X: did not find expected key | docker compose config | Erreur d'indentation, guillemets manquants autour de caractères spéciaux, référence extends incorrecte |
| Échec de pull d'image | pull access denied ou manifest unknown | docker pull <image>:<tag> manuellement | Authentification registre privé manquante ; typo dans le tag ; incompatibilité d'architecture (arm64 vs amd64) |
Playbook de rollback
- Tagger les images de façon immuable : N'écrasez jamais
latestou un tag de version sémantique. Poussezmyorg/api:1.4.0-rc.1,myorg/api:1.4.0,myorg/api:1.4.1-hotfix. - Conserver l'historique Compose dans Git : Chaque changement de configuration est un commit. Le rollback est
git revert <commit>suivi dedocker compose up -d. - Sauvegarde des volumes avant changements stateful : Pour les bases de données, effectuez un dump logique (
pg_dump,mysqldump) ou un snapshot du volume nommé :
docker run --rm -v pgdata:/volume -v $(pwd):/backup alpine tar czf /backup/pgdata-$(date +%F).tar.gz -C /volume .
- Versioning des secrets et configs : Dans Swarm/Kubernetes, faites pivoter les secrets via de nouvelles versions ; conservez l'ID de la version précédente documenté pour un rollback instantané.
Exemple de rollback travaillé : racine en lecture seule casse les écritures temporaires
Changement : Ajout de read_only: true au service api. Échec : Le conteneur plante au démarrage ; les logs montrent OSError: [Errno 30] Read-only file system: '/tmp/cache'. Récupération (moins de 2 minutes) :
# 1. Arrêter le service en échec seulement
docker compose stop api
# 2. Revenir au changement Compose (ou checkout du commit précédent)
git checkout HEAD~1 -- docker-compose.yml
# 3. Redémarrer avec la config connue bonne
docker compose up -d api
# 4. Vérifier
docker compose logs -f api | head -20
curl -f http://127.0.0.1:8000/healthz
Action post-mortem : Ajouter un montage tmpfs pour /tmp à l'itération suivante :
services:
api:
read_only: true
tmpfs:
- /tmp:size=64M,mode=1777
- /var/cache:size=32M
Checklist opérationnelle : hygiène continue
Intégrez cette checklist à votre cadence de revue régulière. Automatisez quand possible (ex. docker image prune -a --filter "until=720h" dans un cron).
Posture de sécurité
- [ ] Capacités :
cap_drop: [ALL]aveccap_addminimal par service. Auditez avecdocker inspect --format '{{.HostConfig.CapDrop}} {{.HostConfig.CapAdd}}' $(docker ps -q). - [ ] Système de fichiers racine :
read_only: truesur tous les services sans état ;tmpfspour les chemins inscriptibles. - [ ] Namespace utilisateur : Exécutez les conteneurs en non-root (
user: "1000:1000"ouUSERdans le Dockerfile). Vérifiez avecdocker exec <c> id. - [ ] Secrets : Aucun secret en clair dans les images, fichiers Compose ou variables d'environnement. Utilisez Docker secrets, HashiCorp Vault ou les gestionnaires de secrets cloud.
- [ ] Exposition réseau : Seulement
127.0.0.1ou réseaux overlay internes ; pas de ports publiés0.0.0.0sauf au niveau du load balancer en bordure.
Efficacité des ressources
- [ ] Limites et réservations : Chaque service a
deploy.resources.limits.memoryetreservations.memory. Limites CPU (cpus: '0.5') pour charges sensibles à la latence. - [ ] Taille des images : Builds multi-étapes ;
docker image lsne montre aucune image > 500 Mo sauf justification (modèles ML, etc.). - [ ] Mise en cache des couches : Arguments de build et ordre
COPYoptimisés ;docker build --no-cacherarement nécessaire. - [ ] Politique de nettoyage : Suppression automatisée des images dangling, conteneurs arrêtés > 24h, volumes inutilisés > 7j.
Persistance et sécurité des données
- [ ] Volumes nommés pour l'état : Pas de montages bind pour données de base de données en production ; volumes nommés avec drivers explicites (local, NFS, CSI).
- [ ] Sauvegarde vérifiée : Test de restauration effectué trimestriellement. Documentez RPO/RTO.
- [ ] Permissions des volumes : Conteneurs
initoudocker run --rm -v data:/data alpine chown -R 999:999 /datapour alignement UID.
Réseau
- [ ] Segmentation des services : Réseaux séparés
frontend,backend,db; les services ne s'attachent qu'aux réseaux dont ils ont besoin. - [ ] DNS et découverte de services : Utilisez les noms de service (
api,db) pas les IPs ;docker compose configmontre la topologienetworks. - [ ] Terminaison TLS : Gérée au reverse proxy (Traefik, Nginx, Caddy) ; trafic interne en clair seulement dans VPC de confiance.
Documentation et traçabilité
- [ ] Fichiers Compose dans Git :
docker-compose.yml,docker-compose.override.yml,.env.example(pas de vrais secrets) versionnés. - [ ] Journal des changements : Chaque PR inclut une section
## Changements Config Dockerdécrivant quoi, pourquoi et étapes de validation. - [ ] Runbooks : Lien depuis les alertes de monitoring vers les sections du runbook (ex. "API OOM Kill → Runbook Section 4.2").
Cadence de revue
- Après chaque incident : Le post-mortem inclut la cause racine de configuration.
- Mensuel : Posture de sécurité (capacités, utilisateurs, secrets, exposition réseau).
- Trimestriel : Checklist complète ci-dessus ; planification de capacité utilisant les tendances
docker stats. - Annuel : Rafraîchissement des images de base (rebuild sur nouvelle version Debian/Alpine), revue de dépréciation pour versions Docker Engine/Compose.
Conclusion
Les erreurs de configuration Docker sont rarement spectaculaires — elles s'accumulent sous forme d'images non taguées, de limites mémoire manquantes, de conteneurs sur-privilégiés et de montages bind non documentés. La différence entre un déploiement fragile et un déploiement résilient réside dans un flux de travail discipliné : inventoriez votre environnement, changez une chose à la fois, validez avec des commandes concrètes, et conservez un chemin de rollback testé. Commencez par un seul service — peut-être celui qui vous a réveillé le mois dernier — et appliquez les étapes de ce guide. Mesurez le avant-après. Passez ensuite au service suivant. Avec le temps, vous remplacez le savoir tribal par des pratiques de configuration répétables et auditable qui survivent au turnover d'équipe et s'étendent à travers les environnements.