Lecture : 11 minutes · Niveau : intermédiaire · Mise à jour : avril 2026
Docker simplifie le déploiement applicatif au point qu’il est devenu un standard implicite pour les PME modernes. Mais entre faire tourner un docker run sur son poste et faire tourner du Docker en production fiable, il y a un fossé. Ce guide rassemble les pratiques opérationnelles qui font la différence.
Voir aussi → DevOps moderne pour PME : guide CI/CD et IaC.
Sommaire
- Dockerfile : ce qui distingue un bon d’un mauvais
- Multi-stage builds : images minces
- Docker Compose pour stack production
- Sécurité des conteneurs
- Registres et publication d’images
- Logs, monitoring, healthchecks
- Persistance des données et backups
- Déploiement et mise à jour sans downtime
- FAQ
1. Dockerfile : ce qui distingue un bon d’un mauvais
Un mauvais Dockerfile produit une image volumineuse, lente à builder, peu sécurisée, qui se reconstruit entièrement à chaque changement mineur.
# Mauvais exemple
FROM ubuntu:24.04
RUN apt update && apt install -y nodejs npm
COPY . /app
WORKDIR /app
RUN npm install
CMD npm start
Problèmes : image énorme (Ubuntu de base), apt-get cache non nettoyé, pas de séparation entre dépendances et code (tout invalide le cache), commande lancée par root, version Node.js imprévisible.
Une version propre :
FROM node:20-alpine
WORKDIR /app
# Installer les dépendances en couche séparée (cache)
COPY package*.json ./
RUN npm ci --omit=dev
# Copier le code après — pas d'invalidation du cache deps
COPY . .
# User non-root
RUN addgroup -g 1001 nodejs && adduser -u 1001 -G nodejs -s /bin/sh -D nodeapp
USER nodeapp
EXPOSE 3000
CMD ["node", "server.js"]
Différences clés : image officielle minimaliste (node:20-alpine), séparation claire installation deps / copie code (cache exploitable), exécution en user non-root.
.dockerignore indispensable
Comme .gitignore, .dockerignore exclut des fichiers du contexte de build :
node_modules
.git
.env
*.log
.DS_Store
coverage
.vscode
Sans .dockerignore, node_modules du host est envoyé au démon Docker (lent et peut écraser celui de l’image).
2. Multi-stage builds : images minces
Pour les langages compilés ou les apps qui nécessitent un build (TypeScript, React, Go, Rust), le multi-stage build sépare l’environnement de compilation de l’image finale.
# Étape 1 : build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Étape 2 : runtime minimal
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]
L’image finale ne contient pas les outils de build, juste les dépendances runtime et le code compilé. Pour un projet Go, l’image finale peut être basée sur scratch (vide) ou gcr.io/distroless/static, faisant moins de 10 Mo.
3. Docker Compose pour stack production
Pour orchestrer plusieurs services sur un même serveur (app + base de données + reverse proxy), Docker Compose reste le choix le plus simple et le plus stable.
# compose.yml
services:
app:
image: ghcr.io/ma-pme/app:${TAG:-latest}
restart: unless-stopped
environment:
DATABASE_URL: postgresql://app:${DB_PASS}@db:5432/app
depends_on:
db:
condition: service_healthy
networks:
- app-net
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASS}
volumes:
- db-data:/var/lib/postgresql/data
networks:
- app-net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 10s
timeout: 5s
retries: 5
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
networks:
- app-net
depends_on:
- app
volumes:
db-data:
caddy-data:
caddy-config:
networks:
app-net:
Points-clés :
– restart: unless-stopped : redémarre automatiquement sauf si arrêté volontairement
– healthcheck sur chaque service : Compose attend que les deps soient saines
– Variables via fichier .env (jamais commité)
– Caddy en reverse proxy : HTTPS automatique avec Let’s Encrypt
– Volumes nommés pour les données persistantes
– Réseau dédié pour isolation
4. Sécurité des conteneurs
Les bases
- User non-root dans l’image (déjà mentionné)
- Images officielles ou de sources de confiance (vérifier les pulls)
- Image tags figés : pas de
latesten prod, mais des tags précis (node:20.18.1-alpineou un SHA) - Pas de secrets dans l’image : passer en variables d’environnement runtime ou en secrets Docker/Compose
- Read-only filesystem quand possible :
read_only: truedans Compose, avec quelquestmpfspour les chemins temporaires
app:
read_only: true
tmpfs:
- /tmp
- /var/run
Scan de vulnérabilités
Trivy (trivy.dev) scanne les images Docker pour les CVE connues :
trivy image ghcr.io/ma-pme/app:latest
# Filtre uniquement les sévérités HIGH/CRITICAL
trivy image --severity HIGH,CRITICAL ghcr.io/ma-pme/app:latest
Intégrer Trivy dans le pipeline CI (GitHub Actions tutoriel) bloque les PR qui introduisent des vulnérabilités critiques.
Limites de ressources
app:
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
memory: 256M
Sans limites, un conteneur peut consommer toute la RAM/CPU du serveur et faire tomber les autres services.
5. Registres et publication d’images
Le GitHub Container Registry (GHCR) est intégré aux dépôts GitHub et gratuit pour usage privé dans les forfaits payants. Docker Hub a un quota de pulls anonymes plus restreint qu’avant — penser à se connecter avec un compte même pour pull des images publiques.
Alternatives : GitLab Container Registry, AWS ECR, GCP Artifact Registry, Azure Container Registry. Ou auto-héberger Harbor (goharbor.io) pour un contrôle total.
# Tag et push
docker build -t ghcr.io/ma-pme/app:v1.2.3 .
echo $GHCR_TOKEN | docker login ghcr.io -u USER --password-stdin
docker push ghcr.io/ma-pme/app:v1.2.3
Stratégie de tagging
Bonne pratique : taguer chaque image avec à la fois le SHA Git complet (référence stable) et un tag sémantique (v1.2.3, latest). Le SHA permet de revenir précisément à un état, le tag sémantique facilite la communication.
6. Logs, monitoring, healthchecks
Logs
Par défaut Docker stocke les logs en JSON local. Limiter pour éviter de saturer le disque :
app:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Pour centraliser les logs (recommandé dès qu’on a plusieurs serveurs) : driver loki ou journald, ou un sidecar comme Promtail qui pousse vers Loki/ELK.
Healthchecks
Les healthchecks Docker permettent à Compose et aux orchestrateurs de savoir si un conteneur est sain. Beaucoup d’images officielles n’en définissent pas — à ajouter explicitement.
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD wget -q --spider http://localhost:3000/health || exit 1
Endpoint /health côté app : vérifie connexions DB, dépendances critiques, retourne 200 si OK.
Monitoring
Pour une stack Docker, Cadvisor + Prometheus + Grafana donne des dashboards riches sur les ressources des conteneurs. Plus simple : Netdata, Glances, ou les métriques natives de l’hébergeur.
7. Persistance des données et backups
Tout ce qui n’est pas dans un volume nommé ou un bind mount est éphémère : perdu au prochain docker compose down -v ou à un redéploiement.
volumes:
db-data: # nommé, géré par Docker
uploads:
driver: local
driver_opts:
type: none
o: bind
device: /var/data/uploads # bind sur un chemin host
Stratégie de backup
Pour une base de données dans un conteneur :
# pg_dump direct
docker compose exec db pg_dump -U app app > backup-$(date +%F).sql
# Avec compression
docker compose exec db pg_dump -U app app | gzip > backup-$(date +%F).sql.gz
Ces dumps doivent partir hors du serveur — copie chiffrée vers du stockage objet (S3, Backblaze B2, Wasabi) avec restic ou rclone, planifiée par cron ou systemd timer.
Tester restauration régulièrement. Un backup non testé n’est pas un backup.
8. Déploiement et mise à jour sans downtime
Avec Docker Compose simple :
# Pull la nouvelle version
docker compose pull
# Redéploie en remplaçant les conteneurs
docker compose up -d
Compose remplace les conteneurs un par un. Pour une application stateless, ça suffit pour de courtes interruptions négligeables. Pour zéro downtime réel : utiliser un reverse proxy avec deux versions backend en parallèle (blue-green) et basculer le trafic.
Rolling update sur plusieurs serveurs
Avec deux serveurs derrière un load balancer (Cloudflare, Caddy avec multiple backends, ou Nginx), retirer un serveur du pool, mettre à jour, remettre, passer au suivant.
# Sur serveur 1
ssh server-1 "cd /opt/app && docker compose pull && docker compose up -d"
# Vérifier la santé avant de passer au suivant
curl https://server-1.example.com/health
Voir aussi → Terraform et Ansible : infrastructure as code en pratique pour orchestrer ce genre de déploiement de manière reproductible.
9. FAQ
Docker Swarm est-il toujours pertinent en 2026 ?
Pour une PME, oui. Docker Swarm est plus simple que Kubernetes, intégré au CLI Docker, et suffisant pour un cluster de quelques nœuds. Il a moins de buzz mais reste maintenu et fonctionnel. Les alternatives modernes simplifiées (Coolify, CapRover, Dokku) sont encore plus accessibles pour des cas d’usage typiques de PME.
Faut-il toujours mettre une base de données dans un conteneur ?
Pour le développement et le staging : oui, par simplicité. Pour la production avec données critiques : ça dépend. Une base managée chez l’hébergeur (Hetzner Managed PostgreSQL, RDS, etc.) délègue la sauvegarde, le patching et la haute disponibilité. Une base auto-hébergée en conteneur fonctionne bien aussi, à condition d’avoir une vraie stratégie de backup, monitoring, et upgrade.
Image Alpine ou Debian-slim : laquelle préférer ?
Alpine produit des images plus petites (utilise musl libc), Debian-slim a plus de compatibilité avec les paquets Linux courants. Pour une app simple : Alpine est très bien. Pour une app qui dépend de bibliothèques C compilées contre glibc (numpy, pandas avec wheels précompilées) : Debian-slim évite des heures de debug.
Mes builds Docker sont lents en CI, comment accélérer ?
Trois leviers : exploiter le cache de couches Docker (ordre des instructions Dockerfile), activer BuildKit avec cache distant (type=gha sur GitHub Actions), utiliser des builders multi-architecture seulement quand vraiment nécessaire. Un bon Dockerfile peut réduire un build de 5 minutes à moins d’une minute.
Comment gérer les secrets dans Docker Compose ?
Plusieurs options : variables d’environnement via fichier .env (le moins sécurisé mais le plus simple), docker secret avec Swarm activé, ou monter en volume un fichier chiffré déchiffré au démarrage. Pour des secrets sensibles : intégrer un gestionnaire de secrets externe (Vault, sops) qui injecte au démarrage du conteneur.
Le conteneur doit-il toujours faire une seule chose ?
Idéalement oui : un processus principal par conteneur, pas de combinaison applicatif + base + reverse proxy dans un seul conteneur. Cela facilite le scaling indépendant, le debug, les mises à jour. L’exception classique : les sidecars qui complètent un conteneur principal (ex: un agent de log qui forwarde les logs).
Articles liés (cluster DevOps moderne)
- 👉 DevOps moderne pour PME : guide CI/CD et IaC (pillar)
- 👉 GitHub Actions : CI/CD pratique pour PME
- 👉 Terraform et Ansible : infrastructure as code en pratique
Article mis à jour le 25 avril 2026. Pour signaler une erreur ou suggérer une amélioration, écrivez-nous.