Déploiement en production avec Docker Compose : 7 stratégies clés des health checks aux mises à jour sans interruption

DevOps

« Ça marche sur ma machine » — Le cimetière des conteneurs en production

Le mantra de chaque développeur : « Ça marche sur ma machine. » Mais quand les conteneurs arrivent en production, le vrai cauchemar commence :

  • Conteneurs silencieusement OOM Killed, ne laissant que Out of memory dans les logs
  • Base de données pas encore prête, conteneurs d'application criant Connection refused
  • Conteneur plante à 3h du matin sans politique de redémarrage — service hors ligne jusqu'au matin
  • Fichiers de logs remplissant les disques, docker logs affichant des dizaines de Go de texte non structuré
  • Identifiants de production en clair dans docker-compose.yml, mots de passe de base de données exposés

Si vous utilisez encore docker compose up -d comme toute votre stratégie de production, cet article est pour vous.

Référence des concepts clés

Concept Objectif Configuration clé en production
Health Check Détecter si un conteneur est vraiment prêt healthcheck + depends_on.condition
Limites de ressources Limiter CPU/mémoire, prévenir la surconsommation deploy.resources.limits
Politique de redémarrage Redémarrage automatique en cas de sortie anormale deploy.restart_policy
Secrets Stockage chiffré des données sensibles secrets + Docker Secret
Pilote de logging Logging structuré + rotation des logs logging.driver + logging.options
Profiles Démarrage sélectif des services par environnement profiles
Watch Synchronisation automatique des modifications de fichiers vers les conteneurs watch (Compose Watch)

5 Défis de production

Défi 1 : Ordre de démarrage des conteneurs incontrôlable

La base de données est encore en cours d'initialisation pendant que le conteneur d'application tente de se connecter, causant des échecs de démarrage. depends_on garantit seulement l'ordre de démarrage, pas la disponibilité du service.

Défi 2 : Expansion illimitée des ressources

Les conteneurs sans limites de ressources sont comme des voitures sans freins. Un seul conteneur avec une fuite mémoire peut consommer toute la mémoire de l'hôte et faire tomber tous les services.

Défi 3 : Le trou noir des logs

Le pilote de logs json-file par défaut ne fait pas de rotation. Après 3 mois d'exécution, /var/lib/docker remplit le disque et tous les services plantent.

Défi 4 : Exposition de données sensibles

Mots de passe de base de données en clair dans les blocs environment, fichiers .env commités dans Git, clés API codées en dur dans les images — ce sont des bombes à retardement pour les incidents de production.

Défi 5 : Les mises à jour signifient un temps d'arrêt

docker compose up -d arrête les anciens conteneurs avant d'en démarrer de nouveaux par défaut, rendant le service indisponible pendant les mises à jour. Pour les services 24/7, c'est inacceptable.

7 Patterns de production

Pattern 1 : Health checks et ordonnancement des dépendances

Problème : depends_on contrôle seulement l'ordre de démarrage, ne garantit pas la disponibilité du service.

Solution : Utiliser healthcheck + depends_on.condition: service_healthy.

services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5

  app:
    image: myapp:latest
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    ports:
      - "3000:3000"

Paramètres clés :

  • interval : Intervalle de vérification, 5-10 secondes recommandées pour la production
  • timeout : Délai d'attente d'une vérification, 3-5 secondes recommandées
  • retries : Échecs consécutifs avant de marquer comme unhealthy
  • start_period : Période de grâce après le démarrage du conteneur, les échecs ne comptent pas

Pattern 2 : Limites de ressources et protection OOM

Problème : Les conteneurs sans limites se disputent les ressources de l'hôte. Un conteneur incontrôlé peut faire tomber toute la machine.

Solution : Utiliser deploy.resources pour définir les limites et réservations de CPU et mémoire.

services:
  app:
    image: myapp:latest
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 10s
      timeout: 5s
      retries: 3

  worker:
    image: myworker:latest
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          memory: 512M

Limites vs Réservations :

  • limits : Plafond strict, dépassé → OOM Kill ou throttling CPU
  • reservations : Garantie souple, l'ordonnanceur essaie de respecter mais n'impose pas

Stratégie de protection OOM :

services:
  critical-service:
    image: critical-app:latest
    deploy:
      resources:
        limits:
          memory: 1G
    cap_add:
      - SYS_PTRACE

Au niveau de l'hôte, configurez vm.overcommit_memory et ajustez les politiques OOM :

# Vérifier le score OOM du conteneur
docker inspect --format='{{.State.OOMKilled}}' <container_id>

# Définir la politique OOM de l'hôte : ne pas tuer les processus critiques
echo -1000 > /proc/<pid>/oom_score_adj

Pattern 3 : Logging structuré et rotation des logs

Problème : Le pilote de logs json-file par défaut ne fait pas de rotation ; les disques se remplissent avec le temps.

Solution : Configurer le pilote de logs + la politique de rotation. Le pilote local est recommandé pour la production.

services:
  app:
    image: myapp:latest
    logging:
      driver: local
      options:
        max-size: "10m"
        max-file: "5"
        tag: "{{.Name}}/{{.ID}}"

  nginx:
    image: nginx:1.27-alpine
    logging:
      driver: json-file
      options:
        max-size: "50m"
        max-file: "10"
        tag: "nginx/{{.Name}}"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro

Comparaison des pilotes de logs :

Pilote Cas d'utilisation Avantages Inconvénients
local Valeur par défaut en production Auto-rotation, stockage compressé Local uniquement
json-file Logs JSON structurés nécessaires Support natif Docker Configuration manuelle de rotation requise
syslog Collecte centralisée des logs Peut envoyer à distance Configuration complexe
fluentd Intégration stack EFK Routage flexible des logs Nécessite le déploiement de Fluentd

Logging structuré au niveau applicatif :

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .

ENV TZ=UTC
ENV LOG_FORMAT=json

CMD ["node", "server.js"]
const logger = {
  info: (msg, meta = {}) => {
    console.log(JSON.stringify({ level: 'info', msg, ts: new Date().toISOString(), ...meta }));
  },
  error: (msg, meta = {}) => {
    console.error(JSON.stringify({ level: 'error', msg, ts: new Date().toISOString(), ...meta }));
  }
};

Pattern 4 : Gestion des Secrets

Problème : Données sensibles stockées en clair dans les fichiers compose ou les variables d'environnement.

Solution : Docker Secrets + variables d'environnement avec suffixe _FILE.

secrets:
  db_password:
    file: ./secrets/db_password.txt
  api_key:
    file: ./secrets/api_key.txt
  jwt_secret:
    file: ./secrets/jwt_secret.txt

services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

  app:
    image: myapp:latest
    environment:
      DATABASE_URL: postgresql://appuser:${DB_PASSWORD}@postgres:5432/appdb
      API_KEY_FILE: /run/secrets/api_key
      JWT_SECRET_FILE: /run/secrets/jwt_secret
    secrets:
      - api_key
      - jwt_secret

Gestion des fichiers de secrets :

# Créer le répertoire des secrets
mkdir -p secrets
chmod 700 secrets

# Écrire les fichiers de secrets
echo "my-super-secret-password-2026" > secrets/db_password.txt
echo "ak-live-xxxx-yyyy-zzzz" > secrets/api_key.txt
echo "jwt-hs256-secret-key-here" > secrets/jwt_secret.txt

# Définir les permissions : lisible uniquement par root
chmod 600 secrets/*.txt

.gitignore doit inclure :

secrets/
*.secret
.env.production
.env.staging

Comparaison Docker Secrets vs .env :

Fonctionnalité Docker Secrets Fichiers .env
Stockage chiffré Oui (en mode Swarm) Non
Permissions de fichiers Restreintes (/run/secrets/) Dépend du système de fichiers
Piste d'audit Oui Non
Synchronisation inter-nœuds Swarm synchronise automatiquement Distribution manuelle
Cas d'utilisation Swarm/production mono-hôte Développement

Pattern 5 : Mises à jour rolling sans interruption

Problème : docker compose up -d arrête les anciens conteneurs avant d'en démarrer de nouveaux par défaut.

Solution : Utiliser docker compose up --no-down + health checks + proxy inverse.

services:
  app:
    image: myapp:${APP_VERSION:-latest}
    deploy:
      replicas: 2
      update_config:
        parallelism: 1
        delay: 10s
        order: start-first
        failure_action: rollback
      rollback_config:
        parallelism: 0
        order: stop-first
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 5s
      timeout: 3s
      retries: 3
      start_period: 15s
    labels:
      - "com.toolsku.app=true"
    ports:
      - "3000-3001:3000"

  nginx:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      app:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/health"]
      interval: 10s
      timeout: 3s
      retries: 3

Configuration du proxy inverse Nginx :

upstream app_backend {
    server app:3000;
}

server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://app_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;

        proxy_next_upstream error timeout http_502 http_503;
    }

    location /health {
        access_log off;
        return 200 'ok';
        add_header Content-Type text/plain;
    }
}

Script de mise à jour sans interruption :

#!/bin/bash
set -euo pipefail

NEW_IMAGE="myapp:v2.0.0"
echo "🚀 Démarrage de la mise à jour sans interruption vers ${NEW_IMAGE}"

# 1. Tirer la nouvelle image
docker compose pull app

# 2. Démarrer les nouveaux conteneurs (sans arrêter les anciens)
docker compose up -d --no-deps --scale app=2 app

# 3. Attendre que les nouveaux conteneurs soient sains
echo "⏳ Attente de la santé des nouveaux conteneurs..."
sleep 15

# 4. Vérifier la santé du nouveau conteneur
for i in $(seq 1 30); do
  if curl -sf http://localhost:3000/health > /dev/null 2>&1; then
    echo "✅ Le nouveau conteneur est sain"
    break
  fi
  if [ $i -eq 30 ]; then
    echo "❌ Health check échoué, retour en arrière..."
    docker compose up -d --no-deps --scale app=1 app
    exit 1
  fi
  sleep 2
done

# 5. Réduire à 1 réplique
docker compose up -d --no-deps --scale app=1 app

echo "🎉 Mise à jour terminée avec succès"

Pattern 6 : Stack de monitoring Prometheus + Grafana

Problème : Faire tourner la production sans monitoring, c'est voler à l'aveugle — vous n'apprenez les problèmes que par les plaintes des utilisateurs.

Solution : Déployer un stack complet de monitoring Prometheus + Grafana.

services:
  prometheus:
    image: prom/prometheus:v2.52.0
    container_name: prometheus
    restart: unless-stopped
    ports:
      - "9090:9090"
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=30d'
      - '--storage.tsdb.retention.size=5GB'
    deploy:
      resources:
        limits:
          memory: 512M
    networks:
      - monitoring

  grafana:
    image: grafana/grafana:11.0.0
    container_name: grafana
    restart: unless-stopped
    ports:
      - "3001:3000"
    environment:
      GF_SECURITY_ADMIN_USER: admin
      GF_SECURITY_ADMIN_PASSWORD_FILE: /run/secrets/grafana_password
    secrets:
      - grafana_password
    volumes:
      - grafana_data:/var/lib/grafana
    deploy:
      resources:
        limits:
          memory: 256M
    depends_on:
      - prometheus
    networks:
      - monitoring

  node-exporter:
    image: prom/node-exporter:v1.8.0
    container_name: node-exporter
    restart: unless-stopped
    ports:
      - "9100:9100"
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - '--path.rootfs=/rootfs'
    networks:
      - monitoring

  alertmanager:
    image: prom/alertmanager:v0.27.0
    container_name: alertmanager
    restart: unless-stopped
    ports:
      - "9093:9093"
    volumes:
      - ./monitoring/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
    networks:
      - monitoring

secrets:
  grafana_password:
    file: ./secrets/grafana_password.txt

volumes:
  prometheus_data:
  grafana_data:

networks:
  monitoring:
    driver: bridge

Configuration de Prometheus :

# monitoring/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "alert_rules.yml"

alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - alertmanager:9093

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100']

  - job_name: 'app'
    static_configs:
      - targets: ['app:3000']
    metrics_path: /metrics

Règles d'alerte :

# monitoring/alert_rules.yml
groups:
  - name: container_alerts
    rules:
      - alert: ContainerDown
        expr: up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Container {{ $labels.instance }} is down"

      - alert: HighMemoryUsage
        expr: process_resident_memory_bytes / (1024 * 1024) > 400
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Memory usage exceeds 400MB on {{ $labels.instance }}"

      - alert: DiskSpaceLow
        expr: node_filesystem_avail_bytes / node_filesystem_size_bytes < 0.1
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Disk space below 10% on {{ $labels.instance }}"

Pattern 7 : Configuration multi-environnement (dev/staging/prod)

Problème : Les configurations dev, staging et production sont mélangées — modifier la configuration d'un environnement risque d'affecter les autres.

Solution : Utiliser docker-compose.override.yml + stratégie de superposition multi-fichiers.

Structure de répertoires :

project/
├── docker-compose.yml              # Configuration de base
├── docker-compose.override.yml     # Surcharge dev (chargée automatiquement)
├── docker-compose.staging.yml      # Surcharge staging
├── docker-compose.prod.yml         # Surcharge production
├── .env                            # Variables d'environnement par défaut
├── .env.staging                    # Variables d'environnement staging
├── .env.prod                       # Variables d'environnement production
├── monitoring/
│   ├── prometheus.yml
│   └── alertmanager.yml
└── secrets/
    ├── db_password.txt
    ├── api_key.txt
    └── grafana_password.txt

Configuration de base docker-compose.yml :

services:
  app:
    image: myapp:${APP_VERSION:-latest}
    environment:
      NODE_ENV: ${NODE_ENV:-development}
      DATABASE_URL: postgresql://appuser:${DB_PASSWORD}@postgres:5432/appdb
      REDIS_URL: redis://redis:6379
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - app-network

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 5s
      timeout: 3s
      retries: 5
    networks:
      - app-network

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    networks:
      - app-network

secrets:
  db_password:
    file: ./secrets/db_password.txt

volumes:
  postgres_data:

networks:
  app-network:
    driver: bridge

Surcharge dev docker-compose.override.yml (chargée automatiquement) :

services:
  app:
    build: .
    volumes:
      - .:/app
      - /app/node_modules
    ports:
      - "3000:3000"
      - "9229:9229"
    environment:
      NODE_ENV: development
      LOG_LEVEL: debug
    deploy:
      resources:
        limits:
          memory: 512M
    command: node --inspect=0.0.0.0:9229 server.js

  adminer:
    image: adminer:latest
    ports:
      - "8080:8080"
    networks:
      - app-network

Surcharge production docker-compose.prod.yml :

services:
  app:
    image: myapp:${APP_VERSION}
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production
      LOG_LEVEL: info
    deploy:
      replicas: 2
      resources:
        limits:
          cpus: '2.0'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
    logging:
      driver: local
      options:
        max-size: "10m"
        max-file: "5"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 15s

  postgres:
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          memory: 1G
    logging:
      driver: local
      options:
        max-size: "50m"
        max-file: "10"

  redis:
    deploy:
      resources:
        limits:
          memory: 512M
    command: redis-server --appendonly yes --maxmemory 400mb --maxmemory-policy allkeys-lru
    volumes:
      - redis_data:/data
    logging:
      driver: local
      options:
        max-size: "10m"
        max-file: "3"

  nginx:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.prod.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      app:
        condition: service_healthy
    deploy:
      resources:
        limits:
          memory: 128M
    logging:
      driver: local
      options:
        max-size: "50m"
        max-file: "10"

volumes:
  redis_data:

Commandes de démarrage :

# Développement (charge l'override automatiquement)
docker compose up -d

# Staging
docker compose -f docker-compose.yml -f docker-compose.staging.yml --env-file .env.staging up -d

# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod up -d

5 Pièges courants

Piège 1 : depends_on ne signifie pas que le service est prêt

Faux :

services:
  app:
    depends_on:
      - postgres
    # Le conteneur postgres a démarré, mais la BDD peut ne pas être encore initialisée

Correct :

services:
  app:
    depends_on:
      postgres:
        condition: service_healthy
  postgres:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s

Piège 2 : Pas de limites de ressources

Faux :

services:
  app:
    image: myapp:latest
    # Sans limites de ressources — une fuite mémoire peut consommer tout l'hôte

Correct :

services:
  app:
    image: myapp:latest
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 128M

Piège 3 : Pas de rotation des logs

Faux :

services:
  app:
    image: myapp:latest
    # Pilote json-file par défaut, les logs grandissent indéfiniment

Correct :

services:
  app:
    image: myapp:latest
    logging:
      driver: local
      options:
        max-size: "10m"
        max-file: "5"

Piège 4 : Données sensibles en clair

Faux :

services:
  postgres:
    environment:
      POSTGRES_PASSWORD: "my-secret-password-123"
      # Mot de passe en clair dans le fichier compose — catastrophe si commité dans Git

Correct :

services:
  postgres:
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

Piège 5 : Utiliser le tag latest

Faux :

services:
  app:
    image: myapp:latest
    # Chaque pull peut obtenir une image différente — non reproductible

Correct :

services:
  app:
    image: myapp:2.1.0
    # Ou utiliser une variable pour le contrôle de version
    image: myapp:${APP_VERSION:-2.1.0}

Référence de résolution d'erreurs

Message d'erreur Cause Solution
OOMKilled Le conteneur a dépassé la limite mémoire Augmenter la limite memory ou optimiser l'utilisation mémoire de l'app
Connection refused Service dépendant non prêt Ajouter healthcheck + depends_on.condition
no space left on device Logs/images remplissant le disque Configurer la rotation logging + docker system prune
Boucle de restarting L'application plante au démarrage Vérifier docker logs <id> et confirmer la configuration
permission denied Problème de permissions de fichier/répertoire Vérifier la directive user et les permissions des volumes
port is already allocated Conflit de port Changer le mappage de port ou arrêter le processus en conflit
Statut unhealthy Health check en échec Vérifier que la commande healthcheck est correcte
secret not found Fichier secret manquant S'assurer que le fichier correspondant existe dans secrets/
Cannot connect to the Docker daemon Docker n'est pas en cours d'exécution systemctl start docker
image pulling failed Échec du pull d'image Vérifier le réseau/authentification du registry/orthographe du nom d'image

Optimisation avancée

Docker Compose Watch pour le développement

Compose Watch synchronise automatiquement les modifications de fichiers vers les conteneurs sans reconstruire les images :

services:
  app:
    build: .
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
        - action: rebuild
          path: ./package.json
        - action: sync+restart
          path: ./config
          target: /app/config
# Démarrer le mode watch
docker compose watch

Isolation réseau et sécurité

services:
  app:
    networks:
      - frontend
      - backend

  postgres:
    networks:
      - backend
    # postgres pas dans le réseau frontend — accès externe bloqué

  nginx:
    networks:
      - frontend
    ports:
      - "80:80"

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true
    # internal:true désactive l'accès externe

Optimisation d'images avec les builds multi-étapes

# ---- Étape de build ----
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# ---- Étape d'exécution ----
FROM node:20-alpine AS runner
WORKDIR /app
RUN addgroup -g 1001 -S appgroup && \
    adduser -S appuser -u 1001 -G appgroup

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

USER appuser
EXPOSE 3000

HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

CMD ["node", "dist/server.js"]

Comparaison des outils d'orchestration

Fonctionnalité Docker Compose Kubernetes Nomad Docker Swarm
Complexité Faible Élevée Moyenne Faible
Déploiement mono-hôte ✅ Excellent ❌ Excessif ⚠️ Utilisable ✅ Excellent
Orchestration multi-hôtes ❌ Non supporté ✅ Capacité centrale ✅ Capacité centrale ⚠️ Basique
Auto-scaling ✅ HPA/VPA ⚠️ Manuel
Mises à jour rolling ⚠️ Nécessite des scripts ✅ Natif ✅ Natif ✅ Natif
Service discovery ⚠️ DNS ✅ CoreDNS ✅ Consul ✅ DNS
Orchestration du stockage ✅ CSI ✅ CSI ⚠️ Basique
Courbe d'apprentissage Faible Élevée Moyenne Faible
Échelle adaptée 1-10 services 100+ services 50+ services 10-50 services
Prêt pour la production ✅ Mono-hôte ✅ Grande échelle ✅ Moyenne à grande échelle ⚠️ Communauté en déclin

Recommandation : Utilisez Docker Compose pour la production mono-hôte/petite échelle, Kubernetes pour la grande échelle, Nomad si vous êtes dans l'écosystème HashiCorp. Docker Swarm est de plus en plus marginalisé — non recommandé pour les nouveaux projets.

Résumé

Le déploiement en production avec Docker Compose n'est pas juste docker compose up -d. Les health checks garantissent que les services sont vraiment prêts, les limites de ressources préviennent les avalanches OOM, la rotation des logs évite l'épuisement du disque, les Secrets protègent les données sensibles, les mises à jour sans interruption garantissent la disponibilité 24/7, les stacks de monitoring éliminent les angles morts, et les configurations multi-environnement gardent dev/staging/prod correctement séparés. Maîtrisez ces 7 stratégies, et Docker Compose est tout à fait capable de déploiements en production à petite et moyenne échelle.

Outils recommandés

  • Formateur JSON - Formater les configurations JSON liées à Docker Compose YAML
  • Encodage Base64 - Encoder les Secrets et données de configuration sensibles
  • Calculateur de hachage - Générer des sommes de contrôle pour les fichiers de configuration et assurer la cohérence des déploiements

Essayez ces outils exécutés localement dans le navigateur — aucune inscription requise →

#Docker#Docker Compose#生产部署#容器编排#2026#DevOps