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 :
- Un fichier, tout le monde - Tous les services dans un seul YAML.
- Les noms sont des hostnames -
dbdevientdbdans le réseau. - Dépendances avec healthchecks - Ne pas juste attendre le démarrage.
- Variables d'environnement - Jamais de valeurs en dur.
- Profils pour les rôles - Dev, test, monitoring.
- .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.