ITSkillsCenter
Blog

Docker en production pour PME : guide pratique 2026

12 min de lecture

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

  1. Dockerfile : ce qui distingue un bon d’un mauvais
  2. Multi-stage builds : images minces
  3. Docker Compose pour stack production
  4. Sécurité des conteneurs
  5. Registres et publication d’images
  6. Logs, monitoring, healthchecks
  7. Persistance des données et backups
  8. Déploiement et mise à jour sans downtime
  9. 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 latest en prod, mais des tags précis (node:20.18.1-alpine ou 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: true dans Compose, avec quelques tmpfs pour 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)


Article mis à jour le 25 avril 2026. Pour signaler une erreur ou suggérer une amélioration, écrivez-nous.

Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité