Polar Code 🎭

Command Palette

Search for a command to run...

04
Pièce N°04

Module 4 : Docker Compose - L'Orchestre du Crime

Une ville ne dort pas seule. Elle a ses réseaux : l'électricité, l'eau, les égouts. Un service en appelle un autre. La base de données parle à l'API, qui parle au cache, qui parle au frontend. Docker Compose, c'est le plan de tous ces tuyaux.

Introduction : Quand un Seul Conteneur ne Suffit Plus

Tu as une appli web.
Elle a besoin :

  • D'une base de données (PostgreSQL)
  • D'un cache (Redis)
  • D'un backend (Python/Node)
  • D'un frontend (Nginx)

Tu pourrais lancer 4 docker run avec des --link.
Mais c'est du bricolage. Comme attacher des fils avec du scotch.

Docker Compose, c'est le schéma électrique.
Un fichier YAML qui dit : "Voilà comment tout se connecte."


4.1 Le Fichier docker-compose.yml - Le Plan du Braquage

Structure de Base

# Version (3.x recommandée)
version: '3.8'

# Les services = les conteneurs
services:
  # Service 1 : La base de données
  db:
    image: postgres:15-alpine
    container_name: postgres_db
    environment:
      POSTGRES_USER: detective
      POSTGRES_PASSWORD: secret123
      POSTGRES_DB: cold_cases
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - investigation_net
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U detective"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Service 2 : Le cache
  cache:
    image: redis:7-alpine
    container_name: redis_cache
    networks:
      - investigation_net
    command: redis-server --appendonly yes
    volumes:
      - cache_data:/data

  # Service 3 : L'API (build local)
  api:
    build: ./backend  # Dockerfile dans ce dossier
    container_name: cold_cases_api
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    environment:
      DATABASE_URL: postgresql://detective:secret123@db:5432/cold_cases
      REDIS_URL: redis://cache:6379
    volumes:
      - ./backend:/app:ro
      - /app/node_modules
    networks:
      - investigation_net
    ports:
      - "3000:3000"
    restart: unless-stopped

  # Service 4 : Le frontend
  frontend:
    image: nginx:alpine
    container_name: cold_cases_ui
    volumes:
      - ./frontend/dist:/usr/share/nginx/html:ro
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    networks:
      - investigation_net
    ports:
      - "80:80"
    depends_on:
      - api

# Les réseaux (les quartiers)
networks:
  investigation_net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/24

# Les volumes (les caves persistantes)
volumes:
  db_data:
    driver: local
  cache_data:
    driver: local

Les Sections Clés - Décryptage

1. version
Pas la version de Docker Compose, mais la version du schéma.

  • '2.x' : Ancien, mais encore utilisé
  • '3.x' (3.8 recommandé) : Moderne, toutes les fonctionnalités

2. services
Chaque service = un conteneur.
Le nom (db, api) devient le hostname dans le réseau.

3. networks
Par défaut, Compose crée un réseau.
Tous les services y sont connectés.
Ils peuvent se parler par leur nom de service.

4. volumes
Les données qui survivent.
Déclarées ici, créées automatiquement.


4.2 Commandes Docker Compose - Les Ordres du Chef

Les Bases

# Lancer tous les services (détaché)
docker-compose up -d

# Lancer avec rebuild
docker-compose up -d --build

# Lancer seulement certains services
docker-compose up -d db cache

# Arrêter tout
docker-compose down

# Arrêter et supprimer les volumes (ATTENTION !)
docker-compose down -v

# Voir les logs
docker-compose logs
docker-compose logs -f  # Follow
docker-compose logs api  # Seulement l'API

# Voir les processus
docker-compose ps
# NAME                COMMAND                  SERVICE   STATUS   PORTS
# cold_cases_api      "docker-entrypoint.s…"   api       Up       0.0.0.0:3000->3000/tcp

# Exécuter une commande dans un service
docker-compose exec api bash
docker-compose exec db psql -U detective cold_cases

# Redémarrer un service
docker-compose restart api

# Échelle (plusieurs instances)
docker-compose up -d --scale api=3
# Mais besoin d'un load balancer...

Gestion Avancée

# Voir la configuration résolue
docker-compose config
# Montre le YAML après interpolation des variables

# Pull des images
docker-compose pull

# Build sans lancer
docker-compose build

# Pause/Unpause
docker-compose pause api
docker-compose unpause api

# Copier des fichiers
docker-compose cp api:/app/logs ./local_logs

# Top (processus)
docker-compose top

4.3 Fonctionnalités Avancées - Les Petits Détails qui Comptent

Variables d'Environnement et Fichiers .env

docker-compose.yml :

services:
  db:
    image: postgres:${POSTGRES_VERSION:-15}-alpine
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}

.env (à la racine) :

# Fichier .env (NE PAS COMMITER !)
POSTGRES_VERSION=15
DB_USER=detective
DB_PASSWORD=change_this_in_production
DB_NAME=cold_cases
API_PORT=3000

Fichiers .env multiples :

# Par environnement
docker-compose --env-file .env.production up

Healthchecks - Les Signes Vitaux

services:
  db:
    image: postgres:15-alpine
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U detective || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s  # Temps avant de commencer à checker

  api:
    depends_on:
      db:
        condition: service_healthy  # Attendre que db soit healthy
      cache:
        condition: service_started  # Juste démarré

Ressources et Limites

services:
  api:
    deploy:  # Section deploy (Swarm/K8s, mais partiellement supporté)
      resources:
        limits:
          cpus: '0.50'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M
    # Ou directement (toujours fonctionne) :
    mem_limit: 512m
    mem_reservation: 256m
    cpus: 0.5

Profils - Les Scénarios

services:
  db:
    # Toujours lancé
    
  api:
    # Toujours lancé
    
  dev-tools:
    image: node:18
    profiles: ["dev"]  # Seulement avec --profile dev
    volumes:
      - ./:/app
    
  tests:
    build: ./tests
    profiles: ["test"]
    depends_on:
      - db
# Lancer avec profils
docker-compose --profile dev up
docker-compose --profile dev --profile test up

Extends - L'Héritage

docker-compose.base.yml :

services:
  base-service:
    image: alpine:latest
    environment:
      COMMON_VAR: value

docker-compose.yml :

version: '3.8'
services:
  service1:
    extends:
      file: docker-compose.base.yml
      service: base-service
    environment:
      SPECIFIC_VAR: value1
  
  service2:
    extends:
      file: docker-compose.base.yml
      service: base-service
    environment:
      SPECIFIC_VAR: value2

Secrets (Swarm) - Les Informations Sensibles

services:
  api:
    image: myapp:latest
    secrets:
      - db_password
    environment:
      DB_PASSWORD_FILE: /run/secrets/db_password

secrets:
  db_password:
    file: ./db_password.txt  # Fichier sur l'hôte

4.4 Cas Pratique : Application de Surveillance Urbaine

Structure des Fichiers

surveillance_urbaine/
├── docker-compose.yml
├── .env
├── backend/
│   ├── Dockerfile
│   └── src/
├── frontend/
│   ├── Dockerfile
│   └── dist/
├── nginx/
│   └── nginx.conf
├── prometheus/
│   └── prometheus.yml
└── grafana/
    └── provisioning/

docker-compose.yml Complet

version: '3.8'

services:
  # --- STOCKAGE ---
  postgres:
    image: postgres:15-alpine
    container_name: surveillance_db
    environment:
      POSTGRES_USER: ${DB_USER:-watcher}
      POSTGRES_PASSWORD: ${DB_PASSWORD:-change_me}
      POSTGRES_DB: surveillance
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    networks:
      - surveillance_net
    ports:
      - "${DB_PORT:-5432}:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-watcher}"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    container_name: surveillance_cache
    command: redis-server --requirepass ${REDIS_PASSWORD:-secret123}
    volumes:
      - redis_data:/data
    networks:
      - surveillance_net
    ports:
      - "${REDIS_PORT:-6379}:6379"
    restart: unless-stopped

  # --- APPLICATION ---
  api:
    build: ./backend
    container_name: surveillance_api
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    environment:
      NODE_ENV: ${NODE_ENV:-production}
      DATABASE_URL: postgresql://${DB_USER:-watcher}:${DB_PASSWORD:-change_me}@postgres:5432/surveillance
      REDIS_URL: redis://:${REDIS_PASSWORD:-secret123}@redis:6379
      JWT_SECRET: ${JWT_SECRET:-very_secret_key}
    volumes:
      - ./backend:/app:ro
      - /app/node_modules
      - api_logs:/var/log/app
    networks:
      - surveillance_net
    ports:
      - "${API_PORT:-3000}:3000"
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.api.rule=Host(`api.surveillance.local`)"

  # --- FRONTEND ---
  frontend:
    build: ./frontend
    container_name: surveillance_frontend
    depends_on:
      - api
    volumes:
      - ./frontend:/app:ro
      - /app/node_modules
    networks:
      - surveillance_net
    ports:
      - "${FRONTEND_PORT:-8080}:8080"
    restart: unless-stopped

  # --- REVERSE PROXY ---
  nginx:
    image: nginx:alpine
    container_name: surveillance_proxy
    depends_on:
      - api
      - frontend
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - nginx_logs:/var/log/nginx
    networks:
      - surveillance_net
    ports:
      - "80:80"
      - "443:443"
    restart: unless-stopped

  # --- MONITORING ---
  prometheus:
    image: prom/prometheus:latest
    container_name: surveillance_prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/usr/share/prometheus/console_libraries'
      - '--web.console.templates=/usr/share/prometheus/consoles'
      - '--web.enable-lifecycle'
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus_data:/prometheus
    networks:
      - surveillance_net
    ports:
      - "${PROMETHEUS_PORT:-9090}:9090"
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    container_name: surveillance_grafana
    environment:
      GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:-admin}
      GF_INSTALL_PLUGINS: grafana-piechart-panel
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
    networks:
      - surveillance_net
    ports:
      - "${GRAFANA_PORT:-3001}:3000"
    restart: unless-stopped
    depends_on:
      - prometheus

  # --- UTILITAIRES (dev only) ---
  pgadmin:
    image: dpage/pgadmin4:latest
    container_name: surveillance_pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL:-admin@surveillance.local}
      PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD:-admin}
    volumes:
      - pgadmin_data:/var/lib/pgadmin
    networks:
      - surveillance_net
    ports:
      - "${PGADMIN_PORT:-5050}:80"
    profiles:
      - dev
    restart: unless-stopped

  redis-commander:
    image: rediscommander/redis-commander:latest
    container_name: surveillance_redis_commander
    environment:
      REDIS_HOSTS: local:redis:6379:0:${REDIS_PASSWORD:-secret123}
    networks:
      - surveillance_net
    ports:
      - "${REDIS_COMMANDER_PORT:-8081}:8081"
    profiles:
      - dev
    restart: unless-stopped

networks:
  surveillance_net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.22.0.0/24

volumes:
  postgres_data:
    driver: local
  redis_data:
    driver: local
  api_logs:
    driver: local
  nginx_logs:
    driver: local
  prometheus_data:
    driver: local
  grafana_data:
    driver: local
  pgadmin_data:
    driver: local

Scripts d'Utilisation

start.sh :

#!/bin/bash

# Charger les variables d'environnement
set -a
source .env
set +a

echo "🔍 Démarrage du système de surveillance urbaine..."

# Construire les images si nécessaire
if [ "$1" = "--build" ]; then
    echo "🏗️  Construction des images..."
    docker-compose build
fi

# Démarrer les services de base
echo "🚀 Lancement des services principaux..."
docker-compose up -d postgres redis api frontend nginx

# Ajouter le monitoring si demandé
if [ "$2" = "--monitoring" ]; then
    echo "📊 Ajout du monitoring..."
    docker-compose up -d prometheus grafana
fi

# Ajouter les outils dev si en dev
if [ "${NODE_ENV:-production}" = "development" ]; then
    echo "🔧 Ajout des outils de développement..."
    docker-compose --profile dev up -d pgadmin redis-commander
fi

echo "✅ Système démarré."
echo ""
echo "📡 Services disponibles :"
echo "  - Frontend: http://localhost:${FRONTEND_PORT:-8080}"
echo "  - API: http://localhost:${API_PORT:-3000}"
echo "  - PostgreSQL: localhost:${DB_PORT:-5432}"
echo "  - Redis: localhost:${REDIS_PORT:-6379}"
[ "${NODE_ENV:-production}" = "development" ] && \
    echo "  - pgAdmin: http://localhost:${PGADMIN_PORT:-5050}" && \
    echo "  - Redis Commander: http://localhost:${REDIS_COMMANDER_PORT:-8081}"
[ "$2" = "--monitoring" ] && \
    echo "  - Prometheus: http://localhost:${PROMETHEUS_PORT:-9090}" && \
    echo "  - Grafana: http://localhost:${GRAFANA_PORT:-3001}"

stop.sh :

#!/bin/bash

echo "🛑 Arrêt du système de surveillance..."

# Garder les données (ne pas supprimer les volumes)
docker-compose down

# Ou tout supprimer (ATTENTION)
# docker-compose down -v --rmi all

echo "✅ Système arrêté."

logs.sh :

#!/bin/bash

SERVICE=${1:-all}

if [ "$SERVICE" = "all" ]; then
    docker-compose logs -f --tail=100
else
    docker-compose logs -f --tail=100 $SERVICE
fi

4.5 Bonnes Pratiques - Les Règles de l'Orchestre

1. Séparation des Préoccupations

  • Un service = un rôle
  • Pas de "monolithe en conteneur"

2. Gestion des Secrets

# MAUVAIS (en clair dans le YAML)
environment:
  PASSWORD: secret123

# BON (variables d'environnement)
environment:
  PASSWORD: ${DB_PASSWORD}

# MEILLEUR (fichiers de secrets avec Docker Swarm/K8s)

3. Ordre de Démarrage

depends_on:
  db:
    condition: service_healthy  # Pas juste started
  cache:
    condition: service_started

4. Logging Centralisé

services:
  api:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

5. Versionnage

# Nommer les images avec tags
docker-compose build --build-arg VERSION=1.2.3
# Puis push vers un registry

6. Multi-environnements

docker-compose.yml           # Base
docker-compose.override.yml  # Développement (auto-chargé)
docker-compose.prod.yml      # Production

docker-compose.override.yml :

version: '3.8'
services:
  api:
    volumes:
      - ./api:/app  # Mount hot-reload en dev
    environment:
      NODE_ENV: development
# Dev (charge automatiquement l'override)
docker-compose up

# Prod (spécifique)
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up

Conclusion du Module

Docker Compose, c'est le scénario.
Le plan qui dit qui fait quoi, quand, et avec qui.

Les règles du chef d'orchestre :

  1. Un fichier, tout le monde - Tous les services dans un seul YAML.
  2. Les noms sont des hostnames - db devient db dans le réseau.
  3. Dépendances avec healthchecks - Ne pas juste attendre le démarrage.
  4. Variables d'environnement - Jamais de valeurs en dur.
  5. Profils pour les rôles - Dev, test, monitoring.
  6. .env pour les secrets - Mais mieux : un vrai gestionnaire de secrets.

Maintenant tu peux orchestrer.
Faire travailler ensemble les conteneurs.
Comme une équipe bien rodée.

La ville est sous surveillance.
Tous les systèmes sont connectés.
Tous les tuyaux sont en place.

Prochain module : Docker en Production.
Les pièges, les solutions, les outils pro.
Quand le jeu devient sérieux.