Module 3 : Docker Compose
Objectifs du Module
- Maîtriser la syntaxe Docker Compose
- Orchestrer des applications multi-containers
- Gérer les environnements
- Configurer networks et volumes
Durée : 3 heures
1. Introduction
# docker-compose.yml - Structure de base
version: '3.8' # Optionnel depuis Compose v2
services:
web:
image: nginx:alpine
ports:
- "80:80"
api:
build: ./api
environment:
- DATABASE_URL=postgres://db:5432
depends_on:
- db
db:
image: postgres:15
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
2. Services
2.1 Configuration Complète
services:
api:
# Build depuis Dockerfile
build:
context: ./api
dockerfile: Dockerfile
args:
- VERSION=1.0
target: production
# Ou image existante
image: myregistry/api:latest
# Container settings
container_name: myapp-api
hostname: api
restart: unless-stopped
# Ports
ports:
- "8080:8080" # host:container
- "127.0.0.1:9090:9090" # localhost only
# Environnement
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
env_file:
- .env
- .env.local
# Volumes
volumes:
- ./src:/app/src:ro # Bind mount (dev)
- node_modules:/app/node_modules
- /app/dist # Anonymous volume
# Réseau
networks:
- frontend
- backend
# Dépendances
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
# Health check
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Resources
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
# Commande
command: ["npm", "start"]
entrypoint: ["/entrypoint.sh"]
2.2 Depends On avec Conditions
services:
api:
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
migration:
condition: service_completed_successfully
db:
image: postgres:15
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
migration:
image: myapp/migration
command: ["migrate", "up"]
3. Networks et Volumes
3.1 Networks
services:
frontend:
networks:
- public
api:
networks:
- public
- private
db:
networks:
- private
networks:
public:
driver: bridge
private:
driver: bridge
internal: true # Pas d'accès externe
3.2 Volumes
services:
db:
volumes:
# Named volume
- db_data:/var/lib/postgresql/data
# Bind mount
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
# tmpfs
- type: tmpfs
target: /tmp
volumes:
db_data:
driver: local
driver_opts:
type: none
o: bind
device: /path/on/host
4. Variables et Environnement
4.1 Fichier .env
# docker-compose.yml
services:
db:
image: postgres:${POSTGRES_VERSION:-14}
environment:
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
4.2 Profiles
services:
api:
# Toujours démarré
debug:
image: busybox
profiles:
- debug
test:
image: myapp/test
profiles:
- test
5. Commandes
# Démarrer
docker compose up
docker compose up -d
docker compose up --build
# Arrêter
docker compose down
docker compose down -v # + volumes
docker compose down --rmi all # + images
# Logs
docker compose logs
docker compose logs -f api
# Exec
docker compose exec api sh
# Scale
docker compose up -d --scale api=3
# Config validation
docker compose config
# Build
docker compose build
docker compose build --no-cache
6. Exemple Complet
# docker-compose.yml
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- api
networks:
- frontend
api:
build:
context: ./api
target: production
environment:
- DATABASE_URL=postgres://postgres:${DB_PASSWORD}@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
networks:
- frontend
- backend
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks:
- backend
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
networks:
- backend
networks:
frontend:
backend:
internal: true
volumes:
db_data:
redis_data:
Exercice : À Vous de Jouer
Mise en Pratique
Objectif : Orchestrer une application complète multi-containers avec Docker Compose
Contexte : Vous devez déployer une stack applicative complète comprenant un frontend nginx, une API Node.js, une base de données PostgreSQL et un cache Redis. L'architecture doit respecter les principes de séparation des réseaux et de persistance des données.
Tâches à réaliser :
- Créer une structure de projet avec les dossiers appropriés (frontend/, api/)
- Écrire un fichier
docker-compose.ymldéfinissant les 4 services (nginx, api, postgres, redis) - Configurer deux réseaux :
frontend(public) etbackend(privé/internal) - Ajouter des volumes nommés pour la persistance de PostgreSQL et Redis
- Configurer les dépendances avec healthchecks pour garantir l'ordre de démarrage
- Créer un fichier
.envpour les variables sensibles - Démarrer la stack complète et vérifier la communication entre services
Critères de validation :
- [ ] Tous les services démarrent sans erreur dans le bon ordre
- [ ] Le frontend nginx ne peut pas accéder directement à la base de données
- [ ] L'API peut communiquer avec postgres et redis
- [ ] Les données persistent après un
docker compose downetup - [ ] Les healthchecks fonctionnent correctement
- [ ] Les logs montrent que les dépendances sont respectées
Solution
Étape 1 : Structure du projet
Étape 2 : Fichier .env
# .env
COMPOSE_PROJECT_NAME=myapp
POSTGRES_VERSION=15
POSTGRES_PASSWORD=mysecretpassword
POSTGRES_DB=appdb
POSTGRES_USER=appuser
REDIS_VERSION=7-alpine
NODE_ENV=production
Étape 3 : docker-compose.yml
# docker-compose.yml
version: '3.8'
services:
# Frontend - Nginx
frontend:
image: nginx:alpine
container_name: myapp-frontend
ports:
- "80:80"
volumes:
- ./frontend/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
api:
condition: service_healthy
networks:
- frontend
restart: unless-stopped
# API - Node.js
api:
image: node:20-alpine
container_name: myapp-api
working_dir: /app
volumes:
- ./api:/app
environment:
- NODE_ENV=${NODE_ENV}
- DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
- REDIS_URL=redis://redis:6379
command: ["node", "server.js"]
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
networks:
- frontend
- backend
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
# Base de données - PostgreSQL
db:
image: postgres:${POSTGRES_VERSION:-15}-alpine
container_name: myapp-db
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./api/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks:
- backend
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
# Cache - Redis
redis:
image: redis:${REDIS_VERSION:-7-alpine}
container_name: myapp-redis
command: redis-server --appendonly yes
volumes:
- redis_data:/data
networks:
- backend
restart: unless-stopped
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # Pas d'accès internet direct
volumes:
postgres_data:
driver: local
redis_data:
driver: local
Étape 4 : Configuration nginx
# frontend/nginx.conf
events {
worker_connections 1024;
}
http {
upstream api {
server api:3000;
}
server {
listen 80;
location / {
proxy_pass http://api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
Étape 5 : API simple (exemple)
// api/server.js
const http = require('http');
const server = http.createServer((req, res) => {
if (req.url === '/health') {
res.writeHead(200);
res.end(JSON.stringify({ status: 'healthy' }));
} else {
res.writeHead(200);
res.end(JSON.stringify({
message: 'Hello from API!',
database: process.env.DATABASE_URL ? 'configured' : 'not configured',
redis: process.env.REDIS_URL ? 'configured' : 'not configured'
}));
}
});
server.listen(3000, () => {
console.log('API listening on port 3000');
});
-- api/init.sql
CREATE TABLE IF NOT EXISTS health_check (
id SERIAL PRIMARY KEY,
checked_at TIMESTAMP DEFAULT NOW()
);
Étape 6 : Démarrage et tests
# Valider la configuration
docker compose config
# Démarrer tous les services
docker compose up -d
# Voir les logs
docker compose logs -f
# Vérifier l'état des services
docker compose ps
# Tester l'application
curl http://localhost
# Vérifier les réseaux
docker network ls
docker network inspect myapp_backend
# Tester la persistance
docker compose exec db psql -U appuser -d appdb -c "SELECT * FROM health_check;"
# Arrêter sans supprimer les volumes
docker compose down
# Redémarrer
docker compose up -d
# Les données doivent être conservées
# Cleanup complet (avec volumes)
docker compose down -v
Points clés :
- Deux réseaux séparent frontend et backend pour la sécurité
internal: trueempêche le réseau backend d'accéder à internet- Healthchecks garantissent que les services sont prêts avant démarrage des dépendants
- Volumes nommés assurent la persistance des données
.envcentralise la configurationdepends_onavec conditions assure l'ordre de démarrage correct
Quiz
- Comment démarrer en arrière-plan ?
- [ ] A. docker compose up -b
- [ ] B. docker compose up -d
-
[ ] C. docker compose start
-
Quelle condition attend que le service soit healthy ?
- [ ] A. service_started
- [ ] B. service_ready
- [ ] C. service_healthy
Réponses : 1-B, 2-C
Précédent : Module 2 - Images
Suivant : Module 4 - Networking
Navigation
| ← Module 2 : Images et Dockerfile | Module 4 : Networking → |