Exécuter des applications multi-conteneurs en production nécessite bien plus qu’un simple fichier docker-compose.yml basique. Bien que Docker Compose soit couramment associé au développement local, c’est un outil puissant pour déployer des charges de travail en production dans des environnements mono-hôte et de petits clusters lorsqu’il est correctement configuré. Ce guide vous accompagne pas à pas pour renforcer votre configuration Docker Compose en production avec des health checks, des politiques de redémarrage, des limites de ressources, une gestion des secrets, un logging centralisé et un proxy inverse Nginx avec terminaison SSL.
Prérequis
- Serveur Linux avec Docker Engine 24+ et Docker Compose v2 installés
- Nom de domaine pointant vers l’adresse IP publique de votre serveur
- Familiarité de base avec les concepts Docker (images, conteneurs, volumes, réseaux)
- Certificat SSL ou volonté d’utiliser Let’s Encrypt avec Certbot
- Accès SSH à votre serveur de production
- Au moins 2 Go de RAM et 2 cœurs CPU disponibles pour votre stack
Configuration Docker Compose pour la Production
Un docker-compose.yml prêt pour la production diffère significativement d’une configuration de développement. Vous avez besoin de politiques de redémarrage explicites, de health checks, de contraintes de ressources et de systèmes de fichiers en lecture seule lorsque c’est possible.
Politiques de Redémarrage
Les politiques de redémarrage garantissent que vos conteneurs se remettent automatiquement des crashes, des OOM kills ou des redémarrages du serveur hôte:
services:
web:
image: myapp:latest
restart: unless-stopped
deploy:
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120s
Utilisez unless-stopped pour la plupart des services. Cela redémarre les conteneurs après les crashes et les redémarrages du serveur, mais respecte les arrêts manuels. Pour les services critiques qui doivent toujours fonctionner, utilisez always. Évitez no en production — cela laisse les conteneurs crashés inactifs jusqu’à une intervention manuelle.
Health Checks
Les health checks permettent à Docker de surveiller si votre application fonctionne réellement, pas seulement si le processus est en cours d’exécution:
services:
api:
image: myapp-api:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Le start_period donne à votre application le temps de s’initialiser avant que Docker ne commence à compter les vérifications échouées. Configurez-le plus haut que le temps de démarrage moyen de votre application. Utilisez des endpoints de santé spécifiques plutôt que de vérifier si un port est ouvert — un processus peut écouter sur un port tout en étant dans un état défaillant.
Limites de Ressources
Sans limites de ressources, un seul conteneur incontrôlé peut consommer toute la mémoire du système et tout faire tomber:
services:
api:
image: myapp-api:latest
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.25"
memory: 128M
Définissez limits au maximum qu’un service devrait utiliser et reservations pour garantir des ressources minimales. Surveillez vos conteneurs avec docker stats pendant une semaine avant de fixer les limites définitives. Des limites trop strictes provoquent des OOM kills et des performances dégradées.
Systèmes de Fichiers en Lecture Seule
Minimisez la surface d’attaque en exécutant les conteneurs avec des systèmes de fichiers racine en lecture seule:
services:
api:
image: myapp-api:latest
read_only: true
tmpfs:
- /tmp
- /var/run
volumes:
- app-data:/app/data
Cela empêche les processus malveillants d’écrire dans le système de fichiers du conteneur. Utilisez tmpfs pour les répertoires nécessitant un accès temporaire en écriture et des volumes nommés pour les données persistantes.
Gestion des Secrets et Variables d’Environnement
Les identifiants codés en dur dans votre fichier compose représentent un risque de sécurité. Docker Compose prend en charge plusieurs approches pour la gestion des secrets.
Utilisation de Fichiers .env
Créez un fichier .env à côté de votre fichier compose:
# .env - Ne JAMAIS commiter ce fichier
POSTGRES_PASSWORD=votre_mot_de_passe_securise
API_SECRET_KEY=autre_valeur_securisee
REDIS_PASSWORD=mot_de_passe_redis
Référencez-les dans votre fichier compose:
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
Ajoutez .env à votre .gitignore immédiatement. Pour les pipelines CI/CD, injectez les variables d’environnement depuis votre gestionnaire de secrets (GitHub Secrets, AWS Secrets Manager ou HashiCorp Vault).
Docker Secrets
Pour une gestion plus sécurisée, utilisez Docker secrets qui sont montés comme fichiers plutôt que comme variables d’environnement:
secrets:
db_password:
file: ./secrets/db_password.txt
services:
db:
image: postgres:16
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
De nombreuses images Docker officielles prennent en charge la convention du suffixe _FILE, lisant le secret depuis un fichier monté au lieu d’une variable d’environnement. Cela garde les identifiants hors de la sortie de docker inspect et des listes de variables d’environnement du processus.
Proxy Inverse et SSL avec Nginx
Exposer les ports d’application directement est non sécurisé et peu flexible. Utilisez Nginx comme proxy inverse avec terminaison SSL:
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- certbot-webroot:/var/www/certbot:ro
- certbot-certs:/etc/letsencrypt:ro
depends_on:
api:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 5s
retries: 3
certbot:
image: certbot/certbot
volumes:
- certbot-webroot:/var/www/certbot
- certbot-certs:/etc/letsencrypt
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done'"
volumes:
certbot-webroot:
certbot-certs:
La configuration Nginx doit rediriger vers vos services internes:
upstream api_backend {
server api:8080;
}
server {
listen 80;
server_name example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://api_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Remarquez que seul le service Nginx expose des ports vers l’hôte. Tous les autres services communiquent via le réseau interne Docker, réduisant considérablement la surface d’attaque.
Logging et Monitoring
Les déploiements en production nécessitent un logging structuré avec rotation pour éviter l’épuisement du disque.
Logging JSON File avec Rotation
services:
api:
image: myapp-api:latest
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
tag: "{{.Name}}"
Sans max-size et max-file, les logs Docker croissent indéfiniment et peuvent remplir votre disque. Le driver json-file est celui par défaut et fonctionne bien pour la plupart des configurations. Pour les environnements multi-hôtes, envisagez de transférer les logs vers un système centralisé.
Transfert vers des Systèmes Externes
Pour l’observabilité en production, transférez les logs vers un service d’agrégation:
services:
api:
image: myapp-api:latest
logging:
driver: syslog
options:
syslog-address: "tcp://logserver:514"
tag: "myapp-api"
Les alternatives incluent les drivers fluentd et gelf pour les stacks ELK ou Graylog. Quel que soit votre choix, configurez toujours la rotation des logs au niveau du daemon Docker comme filet de sécurité dans /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Monitoring de Base
Ajoutez le monitoring des conteneurs avec un stack léger:
services:
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
deploy:
resources:
limits:
memory: 256M
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
deploy:
resources:
limits:
memory: 128M
Comparaison: Docker Compose vs Kubernetes vs Docker Swarm
| Fonctionnalité | Docker Compose | Kubernetes | Docker Swarm |
|---|---|---|---|
| Complexité de configuration | Faible | Élevée | Moyenne |
| Support multi-hôte | Non (hôte unique) | Oui (cluster) | Oui (cluster) |
| Auto-scaling | Non | Oui (HPA) | Limité |
| Découverte de services | Basé sur DNS | DNS + Ingress | Basé sur DNS |
| Gestion des secrets | Basée sur fichiers | Intégrée (etcd) | Intégrée |
| Mises à jour progressives | Manuel | Intégrée | Intégrée |
| Health checks | Oui | Oui (liveness/readiness) | Oui |
| Limites de ressources | Oui | Oui (requests/limits) | Oui |
| Courbe d’apprentissage | Douce | Raide | Modérée |
| Idéal pour | Hôte unique, petites équipes | Grande échelle, multi-équipe | Petits clusters |
Docker Compose est le bon choix lorsque vous déployez sur un serveur unique ou un petit nombre de serveurs, que votre équipe est petite et que vous n’avez pas besoin d’auto-scaling. De nombreux produits SaaS réussis fonctionnent entièrement sur Docker Compose en production.
Scénario Réel
Vous déployez une application web composée d’une API Node.js, d’une base de données PostgreSQL, d’un cache Redis et d’un proxy inverse Nginx. Le serveur dispose de 4 Go de RAM et 2 cœurs CPU.
Voici le fichier compose complet de production:
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- certbot-certs:/etc/letsencrypt:ro
depends_on:
api:
condition: service_healthy
restart: unless-stopped
deploy:
resources:
limits:
cpus: "0.25"
memory: 64M
logging:
driver: json-file
options:
max-size: "5m"
max-file: "3"
api:
image: myapp-api:1.2.3
env_file: .env
secrets:
- db_password
- api_key
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
read_only: true
tmpfs:
- /tmp
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.25"
memory: 128M
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
POSTGRES_DB: myapp
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
deploy:
resources:
limits:
cpus: "0.5"
memory: 1G
reservations:
memory: 256M
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redisdata:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 3
deploy:
resources:
limits:
cpus: "0.25"
memory: 256M
logging:
driver: json-file
options:
max-size: "5m"
max-file: "3"
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt
volumes:
pgdata:
redisdata:
certbot-certs:
Déployez avec:
docker compose -f docker-compose.prod.yml up -d
docker compose -f docker-compose.prod.yml ps
docker compose -f docker-compose.prod.yml logs --tail=50
Cette configuration alloue environ 1,8 Go sur les 4 Go disponibles, laissant de la marge pour le système d’exploitation et les pics d’utilisation.
Pièges et Cas Particuliers
L’ordre de démarrage des conteneurs ne garantit pas la disponibilité. Utiliser depends_on seul n’attend que le démarrage du conteneur, pas que le service interne soit prêt. Combinez toujours depends_on avec condition: service_healthy et des health checks appropriés.
Les conteneurs orphelins s’accumulent. Lorsque vous supprimez un service de votre fichier compose, l’ancien conteneur continue de fonctionner. Exécutez toujours docker compose up -d --remove-orphans pour nettoyer les conteneurs obsolètes.
Les permissions de volumes causent des échecs silencieux. Si votre conteneur s’exécute en tant qu’utilisateur non-root, le volume monté peut ne pas être accessible en écriture. Pré-créez les volumes avec les bonnes propriétés ou utilisez un pattern de conteneur init.
Docker Compose ne télécharge pas les nouvelles images par défaut lors de la reconstruction. L’exécution de docker compose up -d réutilise les images en cache. Utilisez docker compose pull && docker compose up -d pour vous assurer de déployer la dernière version.
L’expansion des variables du fichier .env peut générer des conflits. Docker Compose interpole ${VAR} dans le fichier compose avant de le transmettre au conteneur. Si la configuration de votre application utilise des caractères $, échappez-les avec $$ dans le fichier compose.
La rotation des logs doit être configurée par service et globalement. Les options de log au niveau du service ne remplacent pas les valeurs par défaut du daemon pour les autres conteneurs. Configurez les deux pour une couverture complète.
Les réseaux bridge ont des problèmes de résolution DNS avec les underscores. Les noms de service contenant des underscores peuvent ne pas se résoudre correctement dans les anciennes versions de Docker. Utilisez des tirets dans les noms de service pour une compatibilité maximale.
Résumé
- Utilisez
restart: unless-stoppedet des health checks sur chaque service de production - Définissez des limites de mémoire et CPU avec
deploy.resourcespour éviter les conteneurs incontrôlés - Gérez les secrets avec Docker secrets ou des fichiers
.env— ne codez jamais les identifiants en dur - Placez Nginx comme proxy inverse devant les services applicatifs avec terminaison SSL
- Configurez la rotation des logs avec
max-sizeetmax-filepour éviter l’épuisement du disque - Utilisez
depends_onaveccondition: service_healthypour un ordonnancement fiable du démarrage - Exécutez
docker compose up -d --remove-orphanspour nettoyer les conteneurs obsolètes à chaque déploiement - Docker Compose est prêt pour la production pour les déploiements mono-hôte et les petites équipes