Docker Intermédiaire

Docker en Production : Maîtriser les Containers pour des Déploiements Robustes

Plongez dans les patterns avancés de Docker utilisés par les équipes DevOps professionnelles. Apprenez à construire, optimiser et déployer des containers en production avec les meilleures pratiques de l'industrie.

Preparetoi.academy 60 min

1. Architecture Multi-Stage et Optimisation des Images

Contexte

La construction d'images Docker efficaces est cruciale en production. Les images volumineuses ralentissent les déploiements, consomment plus de bande passante et augmentent les risques de sécurité. Les builds multi-stage permettent de séparer l'environnement de compilation de l'environnement d'exécution, réduisant drastiquement la taille finale tout en maintenant la sécurité. C'est une pratique incontournable dans les CI/CD modernes où chaque KB compte.

Bloc Code Commenté

# Stage 1 : Build environment (compilation)
FROM node:18-alpine AS builder
WORKDIR /app
# Copie les fichiers de dépendances
COPY package*.json ./
# Installation des dépendances (inclut les devDependencies)
RUN npm ci
# Copie le code source
COPY . .
# Build de l'application
RUN npm run build

# Stage 2 : Runtime environment (exécution)
FROM node:18-alpine
WORKDIR /app
# Variables d'environnement de production
ENV NODE_ENV=production
# Copie uniquement les fichiers nécessaires du stage builder
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
# User non-root pour la sécurité
USER node
# Port d'exposition
EXPOSE 3000
# Commande de démarrage
CMD ["node", "dist/index.js"]

Cas Réel Professionnel

Une startup fintech avait une image Node.js de 850MB en production. Après implémentation des builds multi-stage et utilisation d'Alpine, elle est passée à 120MB. Résultat : temps de pull 7x plus rapide, consommation mémoire réduite de 40%, et sécurité améliorée par la réduction de la surface d'attaque. Leurs déploiements Kubernetes passèrent de 3 minutes à 45 secondes.

Astuce Pro

Utilisez toujours npm ci au lieu de npm install dans vos Dockerfiles. Cela installe les versions exactes du fichier package-lock.json, garantissant une reproductibilité absolue à travers les environnements. C'est une différence invisible mais critique en production.


2. Gestion des Secrets et Variables d'Environnement

Contexte

Exposer des secrets (API keys, mots de passe, tokens) dans les Dockerfiles ou les images est une faille de sécurité majeure. En production, la distinction entre les configurations (variables d'environnement) et les secrets (données sensibles) est fondamentale. Docker propose plusieurs mécanismes pour cette gestion, du simple .env aux Docker Secrets en orchestration, chacun adapté à un contexte spécifique. Les équipes DevOps professionnelles ne commettent jamais d'erreur à ce sujet.

Bloc Code Commenté

# Dockerfile SECURISE - ne jamais passer les secrets en ARG ou ENV
FROM python:3.11-slim

WORKDIR /app

# ARG pour les valeurs non-sensibles uniquement (elles disparaissent après build)
ARG BUILD_VERSION=1.0.0
ENV APP_VERSION=${BUILD_VERSION}

# Variables d'environnement vides - seront remplies à runtime
ENV DATABASE_URL=""
ENV API_KEY=""
ENV LOG_LEVEL="INFO"

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

# Healthcheck pour vérifier que le container fonctionne
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD python -c "import requests; requests.get('http://localhost:5000/health')"

EXPOSE 5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]

Exemple de docker-compose avec gestion des secrets

version: '3.9'

services:
  api:
    image: myapp:latest
    environment:
      # Variables non-sensibles
      LOG_LEVEL: "INFO"
      APP_ENV: "production"
      # Secrets chargés depuis un fichier externe
      DATABASE_URL: ${DATABASE_URL}
      API_KEY: ${API_KEY}
    # Montage d'un fichier .env (ne jamais commit en Git !)
    env_file:
      - .env.production
    secrets:
      - db_password
    ports:
      - "5000:5000"

secrets:
  db_password:
    # En production, utiliser Docker Swarm secrets ou Kubernetes secrets
    file: ./secrets/db_password.txt

Cas Réel Professionnel

Une agence web a souffert d'une fuite de données : une API key AWS était loggée dans l'historique Docker. L'attaquant a eu accès aux ressources cloud pendant 4 heures, coûtant $15,000 en usage non autorisé. Après incident, ils ont mis en place : validation de contenu des images pré-push, scan de secrets avec truffleHog, et utilisation stricte de Docker Secrets en production. Zéro incident depuis 2 ans.

Astuce Pro

Utilisez docker secret avec Docker Swarm ou les secrets Kubernetes. Ces systèmes de gestion des secrets garantissent que les données sensibles ne sont jamais écrites sur le disque du container et sont chiffrées en transit et au repos. Pour le développement local, utilisez des fichiers .env exclos de Git avec une ligne dans .gitignore.


3. Networking et Communication Inter-Containers

Contexte

En production, les containers rarement fonctionnent isolés. Ils forment une architecture distribuée où chaque service doit communiquer avec les autres de manière fiable, sécurisée et performante. Docker propose des réseaux (bridge, host, overlay) avec des capacités DNS intégrées et d'équilibrage de charge. Comprendre ces mécanismes est vital pour construire des applications microservices robustes. Une mauvaise configuration peut mener à des problèmes latents détectés seulement en production.

Bloc Code Commenté

# Docker-compose avec networking avancé
version: '3.9'

services:
  # Service frontend
  frontend:
    image: nginx:alpine
    # Réseau personnalisé = service discovery DNS automatique
    networks:
      - app-network
    ports:
      # Port publié uniquement pour le frontend
      - "80:80"
    environment:
      # Communication interne via nom du service
      BACKEND_HOST: backend:3000
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - backend
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  # Service backend
  backend:
    image: myapp:latest
    networks:
      - app-network
    # Port non exposé publiquement (accessible uniquement via le réseau interne)
    expose:
      - "3000"
    environment:
      DATABASE_HOST: db
      DATABASE_PORT: 5432
      REDIS_HOST: cache
      REDIS_PORT: 6379
      # Communication inter-service via hostname
      LOG_SERVICE: logs:8080
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started

  # Base de données
  db:
    image: postgres:15-alpine
    networks:
      - app-network
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: appuser
      # Mot de passe depuis un secret
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      # Volume nommé pour la persistance des données
      - db-data:/var/lib/postgresql/data
      # Script d'initialisation
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    expose:
      - "5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Cache Redis
  cache:
    image: redis:7-alpine
    networks:
      - app-network
    expose:
      - "6379"
    # Configuration Redis via paramètres de commande
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
    volumes:
      - cache-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3

  # Service de logs centralisé
  logs:
    image: elasticsearch:7.14.0
    networks:
      - app-network
    environment:
      discovery.type: single-node
      ES_JAVA_OPTS: "-Xms512m -Xmx512m"
    expose:
      - "8080"
    volumes:
      - logs-data:/usr/share/elasticsearch/data

# Réseau personnalisé pour l'application
networks:
  app-network:
    driver: bridge
    # Configuration pour meilleure isolation et sécurité
    driver_opts:
      com.docker.network.bridge.name: br-app
      com.docker.network.bridge.enable_ip_masquerade: "true"
    ipam:
      config:
        - subnet: 172.25.0.0/16
          gateway: 172.25.0.1

# Volumes nommés pour persistence
volumes:
  db-data:
    driver: local
  cache-data:
    driver: local
  logs-data:
    driver: local

Cas Réel Professionnel

Une plateforme SaaS avait des connexions qui "tombaient" aléatoirement entre le backend et la base de données. Investigation : ils utilisaient localhost en variable d'environnement au lieu du nom de service. Quand Docker devait réallouer les containers, les IPs changeaient mais le code cherchait toujours localhost. Solution : utiliser les noms de service et laisser le DNS intégré résoudre les IPs. Après correction, zéro déconnexion en 6 mois.

Astuce Pro

Ne publiez (avec ports:) que les services qui doivent être accessibles de l'extérieur. Utilisez expose: pour les services internes. Cela crée une "zone démilitarisée" naturelle où seul le frontend est exposé, renforçant la sécurité. De plus, utilisez les noms de service (DNS) plutôt que les IPs : le réseau Docker gère automatiquement les changements.


4. Volumes, Montages et Persistance des Données

Contexte

Les containers sont par nature éphémères : supprimer un container efface ses données internes. Pourtant, en production, les données doivent persister à travers les redémarrages, mises à jour, et défaillances. Docker propose trois mécanismes : les volumes (gérés par Docker), les bind mounts (répertoires hôte), et les tmpfs (RAM). Chacun a des cas d'usage distincts. Une mauvaise stratégie de persistance peut causer des pertes de données critiques ou des problèmes de performance sévères.

Bloc Code Commenté

version: '3.9'

services:
  # Application web stateful
  webapp:
    image: myapp:latest
    volumes:
      # Volume nommé pour les données persistentes
      - app-data:/app/data
      # Bind mount pour la configuration (lisible depuis l'hôte)
      - ./config.json:/app/config.json:ro
      # Dossier de logs avec bind mount (pour debug)
      - ./logs:/app/logs
      # tmpfs pour les fichiers temporaires (performant, en RAM)
      - type: tmpfs
        target: /app/tmp
        tmpfs:
          size: 512M
    environment:
      DATA_PATH: /app/data
      CONFIG_PATH: /app/config.json

  # Base de données avec stratégie de backup
  postgres:
    image: postgres:15-alpine
    volumes:
      # Volume nommé = géré par Docker, recommandé pour production
      - postgres-data:/var/lib/postgresql/data
      # Scripts d'initialisation (bind mount, en read-only)
      - ./init-db:/docker-entrypoint-initdb.d:ro
      # Dossier de backup (bind mount, read-write)
      - ./backups:/backups
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Service de backup automatique
  backup:
    image: postgres:15-alpine
    # Dépend de la DB pour s'assurer qu'elle est up
    depends_on:
      postgres:
        condition: service_healthy
    volumes:
      # Accès au dossier de backup
      - ./backups:/backups
      # Script de backup
      - ./scripts/backup.sh:/backup.sh:ro
    environment:
      PGPASSWORD: ${DB_PASSWORD}
    # Commande cron-like pour backups périodiques
    command: >
      sh -c "
      while true; do
        echo 'Starting backup at '$(date);
        pg_dump -h postgres -U postgres postgres | 
          gzip > /backups/backup_$(date +%Y%m%d_%H%M%S).sql.gz;
        echo 'Backup completed';
        sleep 86400;
      done
      "

volumes:
  # Volume nommé avec driver local (stockage hôte)
  app-data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: ./app_data
  
  # Volume pour PostgreSQL avec options de performance
  postgres-data:
    driver: local
    driver_opts:
      type: tmpfs
      o: size=2G
      # Alternative pour stockage disque : device: /mnt/docker-volumes/postgres

# Script d'initialisation PostgreSQL
# File: ./init-db/01-init.sql
# CREATE SCHEMA IF NOT EXISTS app;
# CREATE TABLE app.users (
#   id SERIAL PRIMARY KEY,
#   username VARCHAR(100) NOT NULL UNIQUE,
#   created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
# );

Cas Réel Professionnel

Une startup fintech a perdu 48 heures de données transactionnelles. Raison : ils utilisaient un bind mount pour PostgreSQL, et l'administrateur serveur a accidentellement supprimé le répertoire hôte pendant une maintenance. Les données étaient parties définitivement. Après incident, migration vers des volumes nommés avec snapshots quotidiens, et test mensuel de restauration depuis backup. Plus jamais de panique.

Astuce Pro

Préférez les volumes nommés aux bind mounts pour les données de production. Les volumes sont gérés par Docker, chiffrés, et supportent les snapshots/backups. Les bind mounts sont utiles seulement pour du développement local ou des configurations en read-only. Et toujours : testez régulièrement vos restaurations depuis backup. Un backup jamais testé n'en est pas un.


5. Logging, Monitoring et Observabilité

Contexte

Un container qui plante silencieusement est une cauchemar opérationnel. En production, il faut visibilité complète : logs applicatifs, métriques système (CPU, mémoire), traces distribuées, et alertes. Docker supporte plusieurs approches : les drivers de logging (json-file, syslog, splunk), les healthchecks, et l'intégration avec des stacks de monitoring (Prometheus, ELK, Jaeger). Une observabilité faible = diagnostic long et coûteux en cas de panne.

Bloc Code Commenté

version: '3.9'

services:
  # Application avec logging configuré
  app:
    image: myapp:latest
    # Configuration du driver de logging
    logging:
      # Driver JSON structuré (recommandé pour production)
      driver: json-file
      options:
        max-size: "10m"      # Rotation tous les 10MB
        max-file: "3"        # Garder 3 fichiers max
        labels: "app=myapp,env=production"
    # Exposition des métriques pour Prometheus
    ports:
      - "9090:9090"  # Port des métriques
    environment:
      LOG_LEVEL: "INFO"
      METRICS_ENABLED: "true"
      METRICS_PORT: "9090"
    # Healthcheck pour détection de problèmes
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    networks:
      - monitoring

  # Stack ELK pour logs centralisés
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.14.0
    environment:
      discovery.type: single-node
      ES_JAVA_OPTS: "-Xms512m -Xmx512m"
      xpack.security.enabled: "false"
    volumes:
      - elasticsearch-data:/usr/share/elasticsearch/data
    ports:
      - "9200:9200"
    networks:
      - monitoring
    healthcheck:
      test: ["CMD-SHELL", "curl -s http://localhost:9200 >/dev/null || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3

  logstash:
    image: docker.elastic.co/logstash/logstash:7.14.0
    volumes:
      - ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf:ro
    environment:
      LS_JAVA_OPTS: "-Xmx256m -Xms256m"
    depends_on:
      elasticsearch:
        condition: service_healthy
    networks:
      - monitoring
    ports:
      - "5000:5000"

  kibana:
    image: docker.elastic.co/kibana/kibana:7.14.0
    environment:
      ELASTICSEARCH_HOSTS: "http://elasticsearch:9200"
      ELASTICSEARCH_USERNAME: "elastic"
      ELASTICSEARCH_PASSWORD: "changeme"
    ports:
      - "5601:5601"
    depends_on:
      elasticsearch:
        condition: service_healthy
    networks:
      - monitoring

  # Prometheus pour métriques
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
    ports:
      - "9091:9090"
    networks:
      - monitoring

  # Grafana pour visualisation
  grafana:
    image: grafana/grafana:latest
    environment:
      GF_SECURITY_ADMIN_PASSWORD: "admin"
      GF_USERS_ALLOW_SIGN_UP: "false"
    ports:
      - "3000:3000"
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana-dashboards:/etc/grafana/provisioning/dashboards:ro
    depends_on:
      - prometheus
    networks:
      - monitoring

volumes:
  elasticsearch-data:
  prometheus-data:
  grafana-data:

networks:
  monitoring:
    driver: bridge

Fichier logstash.conf

# Configuration Logstash pour traiter les logs Docker
input {
  tcp {
    port => 5000
    codec => json
  }
}

filter {
  # Parser les timestamps ISO
  date {
    match => [ "timestamp", "ISO8601" ]
    target => "@timestamp"
  }
  
  # Enrichissement avec labels Docker
  mutate {
    add_field => { "[@metadata][index_name]" => "logs-%{+YYYY.MM.dd}" }
  }
}

output {
  # Envoi vers Elasticsearch
  elasticsearch {
    hosts => ["elasticsearch:9200"]
    index => "%{[@metadata][index_name]}"
  }
  
  # Stdout pour debug
  stdout {
    codec => rubydebug
  }
}

Cas Réel Professionnel

Une plateforme de commerce électronique avait des ralentissements intermittents. Sans observabilité, l'équipe tâtonnait. Après déploiement d'une stack ELK + Prometheus + Grafana, ils ont découvert qu'une requête N+1 sur le service catalogue causait des pics de CPU tous les 15 minutes. Correction simple (cache Redis) qui n'aurait jamais été trouvée sans visibilité complète. ROI du monitoring : 10x.

Astuce Pro

Configurez les limites de taille des logs (max-size, max-file) pour éviter que les disques ne se remplissent. De plus, parsez les logs en JSON structuré (avec champs comme timestamp, level, service, trace_id) plutôt que du texte libre : cela permet des requêtes et agrégations bien plus puissantes dans Elasticsearch ou Loki.


6. Sécurité des Containers : Images, Registres et Scans

Contexte

Les containers héritent des vulnérabilités du code, des dépendances, et des images de base. En production, ignorer la sécurité est criminel : une image compromise peut exposer tous les secrets, données, et infrastructure. Les équipes DevOps professionnelles scannent les images à la construction, stockent les images dans des registres privés/sécurisés, utilisent des images de base minimales (Alpine, distroless), et mettent à jour régulièrement. La sécurité n'est pas optionnelle, c'est une obligation légale (RGPD, SOC2, PCI-DSS).

Bloc Code Commenté

# Dockerfile SECURISE avec scan intégré
FROM python:3.11-slim-bookworm

# Labels pour traçabilité
LABEL maintainer="devops@company.com"
LABEL version="1.0.0"
LABEL description="Production-ready Python app with security hardening"

# Update et patch des vulnérabilités connues
RUN apt-get update && \
    apt-get upgrade -y && \
    apt-get install --no-install-recommends -y \
    ca-certificates \
    curl && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Créer un utilisateur non-root (crucial pour la sécurité)
RUN groupadd -r appuser && useradd -r -g appuser appuser

# Copier requirements et installer dépendances
COPY requirements.txt .
RUN pip install --no-cache-dir \
    --disable-pip-version-check \
    -r requirements.txt

# Copier le code applicatif
COPY --chown=appuser:appuser . .

# Changer vers l'utilisateur non-root
USER appuser

# Expose uniquement les ports nécessaires
EXPOSE 8000

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

# Commande avec exec form (PID 1 pour signaux correctement transmis)
ENTRYPOINT ["python"]
CMD ["-u", "app.py"]

Analyse et scan des images

#!/bin/bash
# Script de sécurité pré-deployment

IMAGE="myapp:latest"

# 1. Build l'image
docker build -t ${IMAGE} .

# 2. Scan avec Trivy (détecte CVEs)
echo "=== Scanning image for vulnerabilities with Trivy ==="
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy:latest image --severity HIGH,CRITICAL ${IMAGE}

# 3. Scan avec Snyk (dépendances et code)
echo "=== Scanning dependencies with Snyk ==="
snyk container test ${IMAGE} --fail-on=high

# 4. Audit avec Anchore (scan complet)
echo "=== Analyzing with Anchore ==="
anchore-cli image add ${IMAGE}
anchore-cli image wait ${IMAGE}
anchore-cli image vuln ${IMAGE} all

# 5. Inspection manuelle avec docker inspect
echo "=== Docker Image Inspection ==="
docker inspect ${IMAGE} | jq '.[0] | {
  Config: .Config.User,
  Cmd: .Config.Cmd,
  Env: .Config.Env,
  Exposed_Ports: .Config.ExposedPorts,
  Volumes: .Config.Volumes
}'

# 6. Vérification des couches sensibles
echo "=== Checking layers for secrets ==="
docker history ${IMAGE} --human --no-trunc | grep -E 'RUN|ENV|ARG'

# 7. Scanner les secrets en dur
docker run --rm -i \
  -v ~/.truffleHog.json:/root/.truffleHog.json:ro \
  trufflesecurity/trufflehog:latest \
  filesystem /tmp/image \
  --json

# 8. Si tous les tests passent, push vers registre sécurisé
if [ $? -eq 0 ]; then
  echo "=== All security checks passed, pushing to registry ==="
  docker tag ${IMAGE} registry.company.com/secure/${IMAGE}
  docker push registry.company.com/secure/${IMAGE}
else
  echo "=== Security checks failed, image rejected ==="
  exit 1
fi

Configuration registre privé sécurisé

# docker-compose pour registre Docker privé avec authentification
version: '3.9'

services:
  registry:
    image: registry:2
    ports:
      - "5000:5000"
    environment:
      # Authentification basique
      REGISTRY_AUTH: htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM: Registry
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
      # TLS/SSL
      REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
      REGISTRY_HTTP_TLS_KEY: /certs/domain.key
      # Scan des images à la push
      REGISTRY_STORAGE: s3
      REGISTRY_STORAGE_S3_BUCKET: docker-registry-images
      REGISTRY_STORAGE_S3_REGION: eu-west-1
    volumes:
      - ./auth:/auth:ro
      - ./certs:/certs:ro
      - registry-data:/var/lib/registry
    networks:
      - secure

  # Garbage collection pour nettoyer les images inutilisées
  registry-cleanup:
    image: registry:2
    entrypoint: registry garbage-collect
    command: /etc/docker/registry/config.yml
    volumes:
      - ./registry-config.yml:/etc/docker/registry/config.yml:ro
      - registry-data:/var/lib/registry
    depends_on:
      - registry
    networks:
      - secure

volumes:
  registry-data:

networks:
  secure:
    driver: bridge

Cas Réel Professionnel

Une grande banque a trouvé une backdoor dans l'une de ses images Docker en production : un développeur avait accidentellement inclus des credentials de développement. L'image s'était propagée à 500+ containers. La perte finale estimée : 2 millions de dollars en dédommagement client et frais légaux. Aujourd'hui, tous les builds sont scrutés avec Trivy + Snyk. Aucune image n'est deployée sans scan de sécurité passant. Le coût du scanning ? Négligeable comparé au risque.

Astuce Pro

Utilisez les images distroless (par Google) ou alpine pour minimiser la surface d'attaque. Une image Alpine est souvent 10x plus petite qu'une image classique, donc 10x moins de vulnérabilités potentielles. Combinez avec un scan automatique en CI/CD : si une image a des CVEs critiques, rejetez-la automatiquement avant le déploiement.


7. Orquestration : Docker Compose en Production

Contexte

Docker Compose est présenté souvent comme un outil de développement, mais peut tout à fait être utilisé en production pour des déploiements single-host ou petite équipe. Avec les bonnes pratiques (version 3.9+, secrets, healthchecks, restart policies, resource limits), Compose offre une alternative légère à Kubernetes pour certains contextes. Comprendre Compose à fond inclut la gestion des mises à jour, du scaling limité, et de la haute disponibilité relative.

Bloc Code Commenté

version: '3.9'

# Configuration des secrets (version production)
secrets:
  db_password:
    file: ./secrets/db_password.txt
  api_key:
    file: ./secrets/api_key.txt
  jwt_secret:
    file: ./secrets/jwt_secret.txt

services:
  # Load balancer / Reverse Proxy
  nginx:
    image: nginx:1.25-alpine
    container_name: prod-nginx
    restart: unless-stopped  # Redémarrage automatique
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
      - nginx-logs:/var/log/nginx
    depends_on:
      - api-1
      - api-2
      - api-3
    networks:
      - production
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
      interval: 10s
      timeout: 5s
      retries: 3
    logging:
      driver: json-file
      options:
        max-size: "20m"
        max-file: "5"
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M

  # API instances (scaling horizontal manual)
  api-1:
    image: myapp:${APP_VERSION:-latest}
    container_name: prod-api-1
    restart: unless-stopped
    environment:
      NODE_ENV: production
      INSTANCE_ID: 1
      DATABASE_HOST: postgres
      DATABASE_NAME: ${DB_NAME}
      DATABASE_USER: ${DB_USER}
      REDIS_HOST: redis
      API_KEY_FILE: /run/secrets/api_key
      JWT_SECRET_FILE: /run/secrets/jwt_secret
    secrets:
      - api_key
      - jwt_secret
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - production
    expose:
Accédez à des centaines d'examens QCM — Découvrir les offres Premium