## Introduction Faire tourner des images Docker en production est souvent plus simple que les opérations qui les entourent. Le plus difficile n'est pas d'écrire un Dockerfile, mais de savoir ce qui tourne réellement, où vivent les données, comment modifier un conteneur en toute sécurité et comment récupérer en cas de problème. Cet article propose une liste de contrôle opérationnelle pour les développeurs, les consultants DevOps et les équipes techniques de startups qui gèrent des images Docker en production. Il relie des commandes concrètes, les résultats attendus, les signaux de défaillance et les décisions de récupération à chaque domaine opérationnel. L'objectif est la sécurité opérationnelle : observer avant de modifier, limiter le rayon d'impact, utiliser des espaces réservés plutôt que des secrets, vérifier le résultat et documenter comment récupérer si l'état attendu n'est pas atteint. Chaque section ci-dessous couvre un domaine spécifique : inventaire de version et d'environnement, configuration sûre, vérification et diagnostic, modes de défaillance et récupération, et une liste de contrôle opérationnelle consolidée. L'article comprend également une section dédiée aux pièges courants, avec des exemples concrets et des correctifs. ## Inventaire de version et d'environnement Avant toute modification, vous avez besoin d'une vue complète de l'environnement Docker. Commencez par collecter la version de Docker, le pilote de stockage et l'état actuel des conteneurs et des images. Cet inventaire en lecture seule vous aide à identifier ce qui tourne, ce qui est arrêté, ce qui consomme des ressources et si l'environnement correspond à vos attentes. Exécutez ces commandes sur tout hôte Docker : ```bash docker version --format '{{.Server.Version}}' docker info --format 'Storage Driver: {{.Driver}}' docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" ``` Exemple de sortie de `docker ps` : ``` NAMES STATUS PORTS web-01 Up 2 hours 0.0.0.0:8080->80/tcp api-01 Up 2 hours 0.0.0.0:3000->3000/tcp redis-01 Up 2 hours 6379/tcp ``` Pour les projets Docker Compose, utilisez `docker compose ps`, `docker compose images` et `docker compose config --services` pour lister les services définis. L'essentiel est de capturer l'état actuel avant tout changement. Notez les noms des conteneurs, les tags d'images, les ports et la durée de fonctionnement. Si un conteneur redémarre en boucle, son statut affichera `Restarting (1) 12 seconds ago` au lieu de `Up`. Vérifiez la version de Docker installée, car certaines fonctionnalités dépendent de la version. Par exemple, BuildKit est devenu le constructeur par défaut dans Docker 23.0. Si vous utilisez une version plus ancienne, certaines commandes ou options peuvent ne pas fonctionner. Exécutez `docker version` sur le serveur et sur votre machine locale si vous déployez à distance. ### Inventaire du stockage des données Un élément critique de l'inventaire de l'environnement est de comprendre où vivent les données persistantes. De nombreux incidents en production surviennent parce que des données ont été écrites dans la couche inscriptible du conteneur et perdues lors de sa suppression. Pour éviter cela, listez tous les montages de chaque conteneur : ```bash docker inspect --format '{{ .Name }}: {{ range .Mounts }}{{ .Type }}:{{ .Source }} -> {{ .Destination }} {{ end }}' $(docker ps -q) ``` Exemple de sortie : ``` /web-01: volume:/var/lib/docker/volumes/app_data/_data -> /var/lib/app /api-01: bind:/home/user/data -> /var/lib/app ``` Un volume nommé tel que `app_data:/var/lib/app` est géré par Docker et survit à la recréation du conteneur. Un montage de type bind comme `./data:/var/lib/app` mappe directement un répertoire hôte. Les montages bind sont pratiques pour le développement, mais peuvent causer des problèmes de permissions en production si le chemin hôte n'est pas disponible sur tous les nœuds. Dans le cadre de l'inventaire, effectuez un test de redémarrage sur un conteneur non critique : arrêtez-le, recréez-le et vérifiez que l'application voit toujours ses fichiers. Si les données disparaissent, le service écrivait dans le système de fichiers du conteneur au lieu d'un volume ou d'un montage. ## Chemin de configuration sûr Modifier la configuration d'un conteneur en production peut entraîner des interruptions ou des pertes de données si cela n'est pas fait avec précaution. Le chemin de configuration sûr consiste à apporter le plus petit changement justifié à un seul élément délimité, et à toujours avoir un moyen de revenir en arrière. Par exemple, si vous devez mettre à jour une variable d'environnement, ne reconstruisez pas toute l'image sauf si nécessaire ; mettez plutôt à jour la configuration du conteneur et testez-la. Un flux de configuration sûr typique pour un seul conteneur : 1. Enregistrez la configuration actuelle : `docker inspect --format '{{json .Config.Env}}'`. 2. Faites une sauvegarde de la définition actuelle du conteneur si vous utilisez Compose : `cp docker-compose.yml docker-compose.yml.bak`. 3. Appliquez le changement. Pour une simple variable d'environnement, vous pouvez exécuter : ```bash docker run -d --name new-web-01 -e "LOG_LEVEL=debug" -p 8080:80 myimage:1.4 ``` Mais en production, il est préférable de mettre à jour le fichier Compose ou le manifeste de déploiement, puis de l'appliquer via `docker compose up -d`. 4. Vérifiez le changement : `docker inspect new-web-01 --format '{{range .Config.Env}}{{println .}}{{end}}' | grep LOG_LEVEL` doit afficher `LOG_LEVEL=debug`. 5. Si le changement échoue, revenez à la sauvegarde et recréez le conteneur d'origine. Évitez de passer des secrets directement sur la ligne de commande ou dans des variables d'environnement lisibles via `docker inspect`. Utilisez les secrets Docker (en mode Swarm) ou un gestionnaire de secrets avec Compose. Par exemple, dans un fichier Compose, référencez un fichier secret : ```yaml services: app: image: myapp:latest secrets: - db_password secrets: db_password: file: ./db_password.txt ``` En mode Swarm, les secrets sont chiffrés et uniquement disponibles pour les conteneurs qui en ont besoin. Un propriétaire doit être désigné pour approuver les changements de configuration. Pour une équipe de startup, un seul responsable DevOps ou ingénieur d'astreinte peut être propriétaire des changements de configuration, avec une revue hebdomadaire pour vérifier les dérives. ### Étiquetage et versionnage des images Un chemin de configuration sûr nécessite également un étiquetage strict des images. N'utilisez jamais `latest` en production, car cela rend les retours en arrière impossibles. Utilisez un schéma de versionnage qui inclut la version de l'application et éventuellement un identifiant de build. Par exemple, `myapp:1.4.2` ou `myapp:1.4.2-20250315`. Lors de la construction, étiquetez explicitement : ```bash docker build -t myapp:1.4.2-20250315 . docker push registry.example.com/myapp:1.4.2-20250315 ``` Ensuite, dans votre fichier Compose, référencez le tag complet. Si vous devez revenir en arrière, changez simplement le tag pour la version précédente et redéployez. ## Vérification et diagnostic Après tout changement, vérifiez que le système se comporte comme prévu. Le diagnostic commence par la vérification du statut des conteneurs, des journaux et de l'utilisation des ressources. Exécutez les commandes suivantes : ```bash docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" docker logs --tail 100 -f docker stats --no-stream ``` La commande `docker stats` affiche l'utilisation du CPU, de la mémoire et du réseau. Une utilisation mémoire élevée peut indiquer une fuite. Les journaux révèlent souvent des erreurs applicatives, comme des échecs de connexion à la base de données. Pour une inspection plus approfondie, utilisez `docker inspect` pour voir les variables d'environnement, les vérifications de santé et les paramètres réseau. Si le conteneur a une vérification de santé définie, récupérez son statut : ```bash docker inspect --format '{{json .State.Health}}' | jq . ``` Cela renvoie le statut de santé (`healthy`, `unhealthy` ou `starting`) et le nombre d'échecs récents. S'il est malsain, inspectez la dernière sortie : ```bash docker inspect --format '{{json .State.Health.Log}}' | jq '.[-1].Output' ``` Pour les services Compose, utilisez `docker compose logs -f ` pour suivre les journaux, et `docker compose exec sh` pour exécuter des commandes à l'intérieur du conteneur sans modifier l'image. Cela est utile pour vérifier les permissions des fichiers ou la connectivité. Par exemple, pour tester si l'application peut atteindre la base de données : ```bash docker compose exec app sh -c 'nc -zv db 5432' ``` Si la connexion échoue, le diagnostic pointe vers un problème de réseau ou de base de données, pas le code de l'application. ### Vérifications de santé Implémentez des vérifications de santé dans votre Dockerfile ou fichier Compose pour automatiser le diagnostic. Une vérification de santé permet à Docker de savoir quand un conteneur est prêt à servir du trafic et quand il échoue. Exemple pour une application web : ```yaml services: web: image: mywebapp:1.0 healthcheck: test: ["CMD", "curl", "-f", "http://localhost/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s ``` Avec cette vérification de santé, Docker marquera le conteneur comme malsain si le point de terminaison échoue trois fois. Dans les environnements orchestrés, les conteneurs malsains peuvent être automatiquement redémarrés ou remplacés. ## Modes de défaillance et récupération Les défaillances en production se répartissent en catégories courantes : le conteneur se termine de manière inattendue, l'application plante, perte de données ou mauvaise configuration réseau. La récupération commence par identifier le mode de défaillance et appliquer la procédure de récupération documentée. ### Arrêts ou redémarrages de conteneurs Si un conteneur est dans une boucle de redémarrage, vérifiez son code de sortie et ses journaux : ```bash docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.ExitCode}}" docker logs --tail 50 ``` Un code de sortie de 137 signifie que le conteneur a été tué (souvent OOM). Augmentez la limite de mémoire ou corrigez l'utilisation mémoire de l'application. Un code de sortie de 1 indique généralement une erreur applicative. Si le conteneur se termine immédiatement, exécutez-le de manière interactive pour voir l'erreur : ```bash docker run -it --rm myimage:tag sh ``` Ensuite, démarrez l'application manuellement à l'intérieur du conteneur. ### Perte de données La perte de données survient généralement lorsque la couche inscriptible du conteneur a été utilisée pour des données persistantes et que le conteneur a été supprimé. Si vous découvrez qu'un volume ou un montage bind manquait, recréez le conteneur avec le bon montage. Mais pour récupérer les données perdues, vous devrez peut-être inspecter le système de fichiers de l'ancien conteneur s'il existe encore. Par exemple, si le conteneur a été arrêté mais pas supprimé, vous pouvez copier des fichiers : ```bash docker cp old-container:/var/lib/app/data ./recovered-data ``` Cependant, si le conteneur a disparu, les données sont perdues à moins que vous n'ayez des sauvegardes. Par conséquent, définissez toujours des volumes dans votre fichier Compose et sauvegardez-les régulièrement. Une commande de sauvegarde simple pour un volume nommé : ```bash docker run --rm -v app_data:/data -v $(pwd):/backup alpine tar czf /backup/app_data_backup.tar.gz -C /data . ``` ### Mauvaise configuration réseau Les problèmes de réseau se manifestent souvent par des conteneurs incapables de communiquer entre eux. Vérifiez la liste des réseaux et inspectez les réseaux des conteneurs : ```bash docker network ls docker inspect --format '{{json .NetworkSettings.Networks}}' | jq . ``` Si un conteneur est attaché au mauvais réseau, reconnectez-le : ```bash docker network connect my-network ``` Pour Compose, assurez-vous que le service est sur le réseau attendu défini dans le fichier Compose. Vérifiez également la résolution DNS à l'intérieur du conteneur : `docker exec getent hosts `. ### Procédure de récupération (runbook) Créez un runbook simple pour chaque service critique. Un runbook est un document qui liste les défaillances courantes, leurs symptômes et les commandes de récupération étape par étape. Par exemple, pour un service web, une entrée de runbook pourrait être : - **Symptôme :** 502 Bad Gateway du proxy inverse. - **Diagnostic :** `docker logs web-01 --tail 20` affiche « connection refused to upstream ». - **Récupération :** Vérifiez si l'application tourne : `docker ps | grep web`. Si non, `docker compose up -d web`. Si elle tourne mais échoue toujours, vérifiez le point de terminaison de santé de l'application et la connectivité à la base de données. Attribuez un propriétaire pour maintenir le runbook de chaque service. Le propriétaire révise et met à jour le runbook mensuellement ou après chaque incident. ## Liste de contrôle opérationnelle Utilisez cette liste comme référence rapide pour les opérations quotidiennes, hebdomadaires et en cas d'incident. ### Vérifications quotidiennes - [ ] Exécutez `docker ps` pour voir le statut des conteneurs ; identifiez les conteneurs en boucle de redémarrage. - [ ] Vérifiez `docker stats --no-stream` pour détecter une utilisation anormale des ressources. - [ ] Consultez `docker logs --tail 100` pour de nouvelles erreurs. - [ ] Vérifiez le statut de santé de tous les conteneurs. - [ ] Assurez-vous que les tâches de sauvegarde ont réussi (vérifiez les journaux de sauvegarde). ### Vérifications hebdomadaires - [ ] Passez en revue les tailles des images : `docker images --format "{{.Repository}}:{{.Tag}} {{.Size}}" | sort -k2 -h` et nettoyez les images inutilisées avec `docker image prune` (mais seulement si c'est sûr). - [ ] Vérifiez les volumes orphelins : `docker volume ls -f dangling=true`. - [ ] Vérifiez que tous les conteneurs en cours d'exécution utilisent des tags spécifiques, pas `latest`. - [ ] Passez en revue la dérive de configuration : comparez les variables d'environnement actuelles avec la documentation. - [ ] Effectuez un test de redémarrage sur un conteneur non critique pour confirmer que les données persistent. ### Avant tout changement - [ ] Enregistrez l'état actuel et les journaux pertinents. - [ ] Identifiez le composant exact (conteneur, service, image) à modifier. - [ ] Déterminez le rayon d'impact : qui est affecté si cela tourne mal ? - [ ] Préparez un plan de retour en arrière : quelles commandes exactes annulent le changement ? - [ ] Attribuez un propriétaire : la personne qui exécutera et vérifiera le changement. - [ ] Informez les parties prenantes si le changement peut entraîner une brève interruption. ### Après tout changement - [ ] Vérifiez le résultat attendu avec une commande ou un signal spécifique. - [ ] Vérifiez les journaux pour de nouvelles erreurs. - [ ] Confirmez que les vérifications de santé passent. - [ ] Mettez à jour le runbook si la procédure a changé. - [ ] Communiquez l'achèvement aux parties prenantes. Cette liste doit être détenue par le responsable des opérations ou l'ingénieur DevOps. Dans une startup, une personne peut détenir toute la liste et la réviser chaque semaine pour s'assurer qu'elle est à jour. ## Pièges courants et comment les éviter Même les équipes expérimentées font des erreurs avec Docker en production. Voici les pièges les plus courants, pourquoi ils surviennent et comment les éviter ou s'en remettre. **1. Utiliser le tag `latest` en production** - **Pourquoi cela arrive :** C'est le défaut et c'est pratique. - **Problème :** Vous ne pouvez pas savoir exactement quelle version tourne, et le retour en arrière est impossible car `latest` change. - **Éviter :** Construisez et déployez toujours avec des tags de version explicites. Utilisez la CI pour étiqueter les images avec une version ou un SHA de commit. - **Récupérer :** Si vous utilisez déjà `latest`, étiquetez immédiatement l'image actuelle avec une version (`docker tag myapp:latest myapp:1.4.2`) et mettez à jour votre déploiement pour utiliser le nouveau tag. **2. Stocker des données dans la couche inscriptible du conteneur** - **Pourquoi cela arrive :** Les développeurs testent localement sans volumes et oublient de les ajouter en production. - **Problème :** Les données sont perdues lorsque le conteneur est supprimé ou mis à jour. - **Éviter :** Définissez des volumes ou des montages bind pour tous les chemins de données persistantes. Testez avec un redémarrage avant la mise en service. - **Récupérer :** Si le conteneur existe encore, utilisez `docker cp` pour extraire les données, puis recréez avec un volume. Sinon, restaurez à partir de la sauvegarde. **3. Exposer des secrets dans les variables d'environnement ou les lignes de commande** - **Pourquoi cela arrive :** C'est le moyen le plus rapide de passer la configuration. - **Problème :** `docker inspect` ou les listes de processus peuvent révéler des secrets. Les journaux peuvent également les capturer. - **Éviter :** Utilisez les secrets Docker en mode Swarm, ou un gestionnaire de secrets avec Compose. Ne passez jamais de mots de passe directement dans `docker run` ou les variables d'environnement Compose. - **Récupérer :** Faites pivoter le secret exposé, mettez à jour la configuration de l'application et assurez-vous qu'aucune copie ne reste dans l'historique du shell ou les journaux. **4. Ignorer les vérifications de santé** - **Pourquoi cela arrive :** Les vérifications de santé sont considérées comme optionnelles ou complexes à écrire. - **Problème :** Sans vérifications de santé, Docker ne peut pas dire si un conteneur fonctionne réellement, ce qui entraîne l'envoi de trafic vers des instances cassées. - **Éviter :** Ajoutez une vérification de santé simple à chaque service. Commencez par une vérification basique de point de terminaison HTTP ou une commande qui vérifie que le processus répond. - **Récupérer :** Ajoutez des vérifications de santé à vos images et redéployez. Ou utilisez une sonde de surveillance externe si vous ne pouvez pas modifier l'image. **5. Ne pas limiter l'utilisation des ressources** - **Pourquoi cela arrive :** Les paramètres Docker par défaut permettent aux conteneurs d'utiliser toutes les ressources de l'hôte. - **Problème :** Une fuite de mémoire dans un conteneur peut affamer les autres et faire planter l'hôte. - **Éviter :** Définissez des limites de mémoire et de CPU dans votre fichier Compose ou la commande `docker run`. Par exemple, `mem_limit: 512m` dans Compose. - **Récupérer :** Définissez immédiatement des limites et redémarrez le conteneur affecté. Surveillez l'utilisation de la mémoire avec `docker stats`. **6. Construire des images en production** - **Pourquoi cela arrive :** Il semble pratique de construire sur l'hôte de production. - **Problème :** Le serveur de production accumule des outils de construction, des couches et des vulnérabilités potentielles. Cela rend également l'environnement incohérent. - **Éviter :** Construisez les images en CI/CD et poussez-les vers un registre. La production ne fait que tirer des images. - **Récupérer :** Supprimez les outils de construction de la production, tirez les images construites depuis le registre et appliquez une politique interdisant les constructions sur les hôtes de production. **7. Ne pas mettre à jour les images** - **Pourquoi cela arrive :** Les équipes sont occupées et oublient d'appliquer les correctifs de sécurité. - **Problème :** Exécuter de vieilles images expose des vulnérabilités connues. - **Éviter :** Planifiez des mises à jour régulières des images, utilisez l'analyse automatisée des vulnérabilités et abonnez-vous aux avis de sécurité. - **Récupérer :** Identifiez les images obsolètes avec `docker images` et vérifiez leurs dates de création, puis planifiez une fenêtre de mise à jour. **8. Modifier la configuration directement sur un conteneur en cours d'exécution** - **Pourquoi cela arrive :** Des corrections rapides comme l'édition d'un fichier à l'intérieur du conteneur. - **Problème :** Les changements sont perdus lors de la recréation du conteneur, et la configuration en cours diverge de la source de vérité (fichier Compose, image). - **Éviter :** Ne modifiez jamais directement les conteneurs en cours d'exécution. Modifiez le Dockerfile ou le fichier Compose, puis recréez le conteneur. - **Récupérer :** Si vous avez apporté des modifications directement, documentez-les et appliquez les mêmes modifications aux fichiers sources, puis recréez le conteneur pour vérifier la cohérence. ## Conclusion Une exploitation d'images Docker de niveau production ne consiste pas à mémoriser toutes les commandes, mais à suivre un processus cohérent et sûr. Commencez par un inventaire complet, apportez des modifications délimitées et réversibles, vérifiez avec des signaux concrets et ayez des procédures de récupération prêtes. La liste de contrôle de cet article sert de point de départ pour le runbook de votre équipe. Choisissez une vérification à faible risque de la liste de contrôle opérationnelle, enregistrez l'état actuel, exécutez la vérification documentée et comparez le résultat avec le signal attendu. Attribuez un seul propriétaire à chaque décision majeure ou élément de la liste, et définissez une cadence de révision régulière (hebdomadaire pour la santé de la liste, mensuelle pour les runbooks). Ce faisant, vous rendez les défaillances visibles, protégez les valeurs sensibles et limitez les changements à la ressource prévue. La fiabilité se construit grâce à ces opérations répétées et disciplinées.