📚 Cet article s’intègre dans notre parcours self-hosting Afrique de l’Ouest. Pour la stratégie complète (Coolify, Hetzner, Docker, sauvegarde, sécurité), voir le guide pilier self-hosting 2026.
Docker Compose en production demande quelques précautions au-delà du dev. Voici les patterns clés en 2026 (informations vérifiées en avril 2026, susceptibles d’évoluer).
Voir notre guide Docker complet.
Structure recommandée
# compose.yml (production)
services:
web:
image: registry.exemple.sn/myapp:latest
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: "0.5"
environment:
DATABASE_URL: ${DATABASE_URL}
networks:
- internal
- traefik-public
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
labels:
- traefik.enable=true
- traefik.http.routers.web.rule=Host(`exemple.sn`)
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
volumes:
- db-data:/var/lib/postgresql/data
networks:
- internal
volumes:
db-data:
networks:
internal:
traefik-public:
external: true
secrets:
db_password:
file: ./secrets/db_password.txt
Override par environnement
compose.yml # base
compose.dev.yml # overrides pour dev (ports exposés, volumes bind)
compose.prod.yml # overrides pour prod (replicas, limits)
# Lancer
docker compose -f compose.yml -f compose.prod.yml up -d
Secrets en production
- JAMAIS de credentials dans le YAML committé
- Utiliser
secretsDocker (fichiers montés en /run/secrets) - Ou variables d’env via
.envfile (gitignored) - Ou Docker Swarm secrets pour multi-host
Updates zero-downtime
# Pull la nouvelle image
docker compose pull web
# Recréer le service avec rolling update (Swarm)
docker service update --image registry/myapp:v2 web
# Sur compose simple : restart
docker compose up -d --no-deps web
Backup base de données
# Cron quotidien
docker exec compose-db-1 pg_dump -U app app | gzip > /backups/db-$(date +%F).sql.gz
Logs centralisés
services:
web:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Pour observabilité avancée, brancher Loki ou Promtail (voir guide Grafana Loki).
Pour approfondir
Vous cherchez un hébergement web sérieux et abordable ?
Hostinger offre des serveurs avec installation WordPress en un clic et un panel simple. Notre équipe l’utilise au quotidien.
Lien d affiliation. Si vous achetez via ce lien, le blog reçoit une petite commission sans surcoût pour vous.
Docker Compose en 2026 : encore pertinent en production ?
Beaucoup d’équipes pensent que Docker Compose est réservé au développement local et qu’il faut basculer sur Kubernetes dès qu’on parle de production. C’est faux pour 80 % des PME ouest-africaines. Si vous gérez 1 à 5 services sur 1 à 3 VPS, Compose v2 (intégré au plugin docker compose, plus le binaire docker-compose historique) est largement suffisant, plus rapide à apprendre, plus économe en ressources et beaucoup moins coûteux en maintenance. Kubernetes a sa place à partir de dizaines de services, autoscaling régional ou équipes de plus de 8 personnes — pas avant.
Ce tutoriel suppose un VPS Linux (Debian 12 ou Ubuntu 24.04 LTS) avec Docker Engine 27+ installé via le dépôt officiel, un nom de domaine pointant vers le VPS, et un projet Node.js, Python ou PHP déjà conteneurisé. Si vous démarrez de zéro, choisissez un Hetzner CX22 (4,51 EUR / 2 960 FCFA / mois) — c’est l’équivalent qualité-prix le plus rentable sur le marché européen accessible depuis l’Afrique de l’Ouest avec une latence de 60 à 90 ms.
Étape 1 — Structurer le projet pour la production
L’erreur classique est de garder un seul compose.yaml qui sert dev, staging et prod. La doctrine moderne sépare en deux fichiers : compose.yaml de base (commun aux trois environnements) et compose.prod.yaml qui surcharge avec les paramètres production.
# /srv/monapp/compose.yaml
services:
app:
image: monapp:latest
restart: unless-stopped
environment:
NODE_ENV: production
networks:
- web
db:
image: postgres:17
restart: unless-stopped
volumes:
- db_data:/var/lib/postgresql/data
networks:
- web
volumes:
db_data:
networks:
web:
Lancez ensuite la prod avec docker compose -f compose.yaml -f compose.prod.yaml up -d. Le second fichier ajoute les ressources, secrets et logs adaptés à la prod sans polluer le dev.
Étape 2 — Limiter les ressources (deploy.resources)
Sans limite, un conteneur qui fuit en mémoire tue tout le VPS. Posez systématiquement des limites :
# compose.prod.yaml
services:
app:
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
memory: 256M
db:
deploy:
resources:
limits:
cpus: "1.5"
memory: 1024M
Sur un Hetzner CX22 (2 vCPU, 4 Go RAM), réservez 30 à 50 % à l’application, 25 % à la base de données, et gardez 1 Go libre pour le système, les logs et les pointes de charge. Vérifiez avec docker stats en cas de doute — c’est un outil sous-utilisé mais redoutable pour le diagnostic.
Étape 3 — Healthchecks pour redémarrages automatiques
Un restart: unless-stopped seul ne suffit pas : Docker redémarre le conteneur uniquement s’il crash, pas s’il devient inerte (deadlock, fuite de fd). Un healthcheck transforme un service zombie en redémarrage actif :
services:
app:
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
Ajoutez côté application un endpoint HTTP /health qui renvoie 200 si tout va bien, 503 sinon. Ne mettez pas de logique lourde dedans (pas de requête en base à chaque check). Combiné à depends_on: condition: service_healthy, votre app attend que Postgres soit réellement prêt avant de démarrer — fini les connection refused au boot.
Étape 4 — Secrets et variables d’environnement sécurisées
Ne jamais commiter de mots de passe dans compose.yaml. Trois techniques classées par sécurité croissante.
Fichier .env (basique mais correct pour PME) : créez /srv/monapp/.env en mode 0600, contenant POSTGRES_PASSWORD=longue_chaine_aleatoire. Compose le charge automatiquement et substitue ${POSTGRES_PASSWORD} dans le yaml.
Docker secrets (recommandé en prod) :
services:
db:
image: postgres:17
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: /srv/monapp/secrets/db_password.txt
Le fichier secret en mode 0400 est monté en lecture seule dans le conteneur. Avantage : il n’apparaît jamais dans docker inspect ni dans la liste des variables d’environnement.
Vault externe (HashiCorp Vault, Bitwarden Secrets Manager) : surdimensionné pour 90 % des PME. Réservé aux équipes avec exigences de conformité fortes.
Étape 5 — Reverse proxy avec Caddy ou Traefik
Pour terminer le HTTPS et router vers les bons conteneurs, Caddy 2.8+ est la solution la plus simple. Une seule ligne de configuration par site.
# compose.prod.yaml ajout
services:
caddy:
image: caddy:2.8
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
networks:
- web
volumes:
caddy_data:
Et dans Caddyfile :
monapp.exemple.sn {
reverse_proxy app:3000
encode gzip zstd
}
Caddy gère Let’s Encrypt automatiquement, redirige HTTP→HTTPS, et active HTTP/3 par défaut. Pour des cas plus complexes (load balancing, A/B testing), passez à Traefik avec sa découverte automatique via labels Docker.
Étape 6 — Logs centralisés avec rotation
Par défaut, Docker écrit les logs dans /var/lib/docker/containers et ne les fait jamais tourner — un service bavard remplit le disque en 3 semaines. Configurez la rotation au niveau du daemon :
// /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "5"
}
}
Rechargez avec systemctl restart docker (impacte les conteneurs : à faire en fenêtre planifiée). Pour un suivi centralisé, exportez vers Loki ou Grafana Cloud (gratuit jusqu’à 50 Go par mois). Une stack promtail + loki + grafana sur le même VPS coûte 200 Mo de RAM et donne une UI complète de recherche.
Étape 7 — Déploiement zero-downtime
Pour une mise à jour sans coupure visible, deux approches selon la criticité.
Méthode simple (suffisante pour 80 % des cas) : docker compose pull && docker compose up -d. Compose redémarre uniquement les conteneurs dont l’image a changé. Pour un service web, l’indisponibilité est de 2 à 5 secondes — acceptable hors heures de pointe.
Méthode rolling avec Caddy : créez deux instances app_blue et app_green, faites pointer Caddy vers la nouvelle version après healthcheck OK, puis arrêtez l’ancienne. Compose ne fait pas ça nativement — utilisez un script shell de 30 lignes ou passez à Docker Swarm avec --update-parallelism 1 si vous tenez à du vrai rolling.
Étape 8 — Sauvegardes automatiques
Tous vos volumes nommés vivent dans /var/lib/docker/volumes. Sauvegardez ce dossier chaque nuit avec votre backup Restic des volumes Docker. Pour Postgres, complétez par un dump applicatif :
docker compose exec -T db pg_dump -U postgres monapp | gzip > /backup/db-$(date +%F).sql.gz
Le dump SQL est plus petit (compression élevée) et restore plus vite qu’un volume binaire en cas de migration vers un autre Postgres. Conservez les deux : volume + dump.
À lire ensuite
Cette base couvre une production solide pour 1 à 5 services. Combinez avec Uptime Kuma pour la supervision et un pipeline CI/CD GitHub Actions qui déclenche docker compose pull via SSH. Pour aller au-delà — autoscaling, multi-région, secrets dynamiques — regardez Coolify (PaaS open source), Dokploy ou directement Kubernetes managé (DigitalOcean Kubernetes à 12 USD soit 7 870 FCFA par mois pour le control plane).
Sécurité : durcir le démon Docker
Quatre paramètres à configurer dès la mise en production. Premièrement, ne jamais exécuter le démon Docker en mode rootless ? Faux paradoxe : pour une PME qui débute, le mode root standard est OK si le VPS est dédié à un seul projet. Pour partager un VPS entre plusieurs équipes, activez Rootless Docker (documentation officielle). Deuxièmement, désactivez les capabilities inutiles dans chaque service avec cap_drop: [ALL] puis cap_add ciblé. Troisièmement, activez security_opt: [no-new-privileges:true] pour bloquer toute escalade de privilèges. Enfin, scannez les images avec Trivy avant chaque déploiement :
docker run --rm aquasec/trivy:latest image monapp:latest --severity HIGH,CRITICAL --exit-code 1
En CI/CD, faites échouer le build si une CVE critique est détectée. C’est 30 secondes ajoutées au pipeline qui empêchent un déploiement vulnérable.
Mises à jour OS et redémarrages planifiés
Un VPS Linux non maintenu est un VPS compromis dans les 6 mois. Activez les unattended-upgrades sur Debian/Ubuntu pour appliquer automatiquement les correctifs de sécurité. Programmez un redémarrage hebdomadaire le dimanche 04 h GMT (heure creuse à Dakar et Abidjan) pour appliquer les mises à jour kernel. Avec restart: unless-stopped sur tous vos services, ils redémarrent automatiquement après le reboot — RTO inférieur à 90 secondes.
Bilan opérationnel
Coût mensuel d’une stack Docker Compose production complète pour une PME ouest-africaine : VPS Hetzner CX22 (4,51 EUR / 2 960 FCFA), backups Backblaze B2 (0,60 USD / 395 FCFA), domaine .sn (12 USD par an / 650 FCFA par mois), monitoring Uptime Kuma auto-hébergé (déjà inclus dans le VPS). Total inférieur à 4 100 FCFA par mois pour une infrastructure capable de servir 50 000 visites mensuelles avec 99,5 % de disponibilité. Comparé à un PaaS comme Render à 7 USD (4 590 FCFA) le service ou Fly.io à des coûts variables imprévisibles, la maîtrise complète d’un Docker Compose bien configuré reste le meilleur compromis pour une équipe technique de 1 à 5 personnes en Afrique de l’Ouest.
Diagnostic : commandes utiles à mémoriser
Un opérateur Docker confirmé maîtrise une dizaine de commandes pour diagnostiquer en moins de 5 minutes. docker compose ps donne l’état de chaque service avec son healthcheck. docker compose logs -f --tail=200 app suit les logs récents en streaming. docker stats affiche en temps réel CPU, RAM, IO réseau et disque par conteneur. docker compose top liste les processus à l’intérieur des conteneurs. docker compose exec app sh ouvre un shell pour inspecter le système de fichiers ou les variables d’environnement. Enfin, docker system df et docker system prune -a --volumes font le ménage des images obsolètes — précieux quand votre /var/lib/docker commence à saturer.
Migration depuis un legacy bare metal
Si vous reprenez une stack legacy (Apache + MySQL + cron PHP) sur un serveur dédié vieillissant, la migration vers Docker Compose se fait progressivement. Commencez par conteneuriser un seul service non critique (un cron ou un worker), validez en parallèle de la prod existante pendant 2 semaines, puis basculez le reste service par service. Cette approche dite « strangler pattern » évite les big bangs catastrophiques. Une migration typique pour une agence digitale de Dakar prend 3 à 5 jours-homme étalés sur un mois, avec zéro coupure visible côté clients.
Profils Compose pour environnements multiples
La directive profiles permet d’activer ou désactiver des services selon le contexte. Pratique pour conserver dans un seul fichier des outils utilisés ponctuellement (Adminer pour Postgres, MailHog pour intercepter les emails en staging) sans qu’ils tournent en permanence en production. Démarrez avec docker compose --profile debug up -d uniquement quand vous en avez besoin. C’est un gain de RAM et de surface d’attaque significatif sur un VPS modeste à Dakar ou Abidjan.
Réseau et isolation entre services
Par défaut, tous les services d’un même Compose partagent le même réseau et peuvent se parler. Pour cloisonner, créez plusieurs réseaux. Exemple typique : un réseau frontend où vit Caddy et l’application, et un réseau backend où vit la base de données. Caddy n’a aucune raison de joindre Postgres directement — l’application sert d’intermédiaire. Si Caddy est compromis, l’attaquant n’a pas accès à la base.