🐳 Module Docker

Maitriser Docker Compose

Orchestrer des applications multi-conteneurs

Utilisez les flèches, cliquez ou glissez pour naviguer

Objectifs de la leçon

1. Comprendre le rôle de Docker Compose

Dans une stack multi-conteneurs

2. Écrire un fichier docker-compose.yml

Services, ports, volumes, variables d'env

3. Configurer depends_on + health checks

Démarrage ordonné des services

4. Utiliser les commandes essentielles

up, down, logs, ps, exec

5. Gérer les variables d'environnement avec .env

Sécurité et configuration externalisée

Plan du cours

1

Rappel du problème

Lancer 3 conteneurs manuellement avec docker run est pénible

2

Docker Compose : l'outil magique

Un fichier YAML pour tout lancer

3

Syntaxe docker-compose.yml

Services, ports, environment, volumes, depends_on, networks

4

Health checks

S'assurer qu'un service est vraiment prêt

5

Live coding

docker-compose.yml pour backend + PostgreSQL + Redis

6

Commandes essentielles & .env

up/down/logs/ps/exec, sécurité des mots de passe

Rappel : lancer 3 conteneurs manuellement

Pour une app backend + PostgreSQL + Redis :

docker network create myapp-net

docker run -d \

--name postgres \

--net myapp-net \

-e POSTGRES_PASSWORD=secret \

-v pgdata:/var/lib/postgresql/data \

postgres:16

docker run -d \

--name redis \

--net myapp-net \

redis:7-alpine

docker run -d \

--name backend \

--net myapp-net \

-p 3000:3000 \

-e DATABASE_URL=postgres://... \

myapp-backend

❌ 10+ lignes de commandes, fragiles, non versionnables

À chaque dev, il faut tout retaper — ou sauvegarder un script shell

La solution : Docker Compose

Un outil pour définir et lancer

des applications multi-conteneurs

en un seul fichier YAML

📄

Déclaratif

Tout dans un fichier

🔄

Reproductible

Même environnement partout

📦

Versionnable

Dans Git avec le code

Structure d'un docker-compose.yml

version: '3.8'

services:

web:

build: .

ports:

- "3000:3000"

environment:

- NODE_ENV=production

db:

image: postgres:16

volumes:

- pgdata:/var/lib/postgresql/data

redis:

image: redis:7-alpine

volumes:

pgdata:

networks:

appnet:

📐 3 sections racines principales : services, volumes, networks

Les services

Chaque conteneur = un service dans Compose

image

Image publique ou privée

image: postgres:16

build

Construire depuis un Dockerfile

build: ./app

container_name

Nom personnalisé du conteneur

container_name: mon-backend

restart

Politique de redémarrage

restart: unless-stopped

Les ports

Mapper les ports du conteneur vers l'hôte

ports:

- "3000:3000" # hôte:conteneur

- "8080:80"

- "5432" # port aléatoire sur l'hôte

1 port = 1 service

Deux services ne peuvent pas partager le même port hôte

Communication interne

Les services se parlent sans exposer de ports

💡 En développement, exposer le port du backend. La BDD et Redis n'ont pas besoin de ports exposés.

Les volumes

Persister les données, partager des fichiers

volumes:

- pgdata:/var/lib/postgresql/data # volume nommé

- ./app:/app # bind mount (dev)

volumes: # section racine

pgdata:

📦 Volume nommé

Persistance gérée par Docker. Idéal pour les bases de données.

📁 Bind mount

Monte un dossier local. Idéal pour le hot-reload en dev.

⚠️ Si vous oubliez la section volumes: racine, Docker crée un volume anonyme !

Variables d'environnement

Configurer les services sans modifier le code

Méthode 1 : directe

environment:

POSTGRES_PASSWORD=secret

NODE_ENV=production

❌ Mot de passe en clair dans le fichier

Méthode 2 : fichier .env

env_file:

- .env

✅ Le .env est dans .gitignore

📌 Les variables sont accessibles dans le conteneur via process.env (Node) ou $_ENV (PHP)

depends_on

Déclarer les dépendances entre services

services:

backend:

build: ./backend

depends_on:

- db

- redis

db:

image: postgres:16

redis:

image: redis:7-alpine

⚠️ Le piège

depends_on démarre db avant backend, mais ne garantit PAS que PostgreSQL est prêt à accepter des connexions.

💡 Solution : combiner depends_on + health check

Health checks

Vérifier qu'un service est vraiment prêt

Un health check = une commande exécutée périodiquement

Si elle retourne 0 → ✅ le service est healthy

Syntaxe d'un health check

db:

image: postgres:16

healthcheck:

test: ["CMD-SHELL", "pg_isready -U monuser"]

interval: 5s

timeout: 5s

retries: 5

start_period: 10s

test

Commande de vérification

interval

Toutes les 5s

retries

5 échecs max

start_period

Grâce au démarrage

💡 Pour Redis : redis-cli ping | grep PONG

depends_on + health check

La combinaison gagnante pour un démarrage ordonné

backend:

depends_on:

db:

condition: service_healthy

redis:

condition: service_healthy

❌ Sans health check

backend démarre → se connecte à PostgreSQL → erreur !

✅ Avec health check

backend attend que PostgreSQL soit healthy → connexion réussie

Les networks

Par défaut, Compose crée un réseau pour vous

🔗 Découverte par nom de service

Dans le code du backend, connectez-vous à db:5432 et non localhost:5432

// ❌ Faux — chaque conteneur a son propre localhost

const client = new Client({ host: 'localhost' });

// ✅ Correct — Docker résout le nom du service

const client = new Client({ host: 'db' });

💡 Pour un réseau personnalisé : networks: - appnet sur chaque service

🔴 Live coding : docker-compose.yml complet

Backend Node/Express + PostgreSQL + Redis

services:

db:

image: postgres:16

container_name: myapp-db

restart: unless-stopped

env_file:

- .env

volumes:

- pgdata:/var/lib/postgresql/data

healthcheck:

test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]

interval: 5s

timeout: 5s

retries: 5

start_period: 10s

redis:

image: redis:7-alpine

container_name: myapp-redis

restart: unless-stopped

healthcheck:

test: ["CMD", "redis-cli", "ping"]

interval: 5s

timeout: 3s

retries: 5

backend:

build: ./backend

container_name: myapp-backend

restart: unless-stopped

ports:

- "3000:3000"

env_file:

- .env

depends_on:

db:

condition: service_healthy

redis:

condition: service_healthy

volumes:

- ./backend:/app # hot-reload

volumes:

pgdata:

Le fichier .env

Variables externalisées, hors du versionnage

📄 .env (dans .gitignore)

POSTGRES_USER=monapp

POSTGRES_PASSWORD=s3cr3t!

POSTGRES_DB=ma_base

DATABASE_URL=postgres://monapp:s3cr3t!@db:5432/ma_base

REDIS_URL=redis://redis:6379

NODE_ENV=development

📄 .env.example (versionné)

POSTGRES_USER=monapp

POSTGRES_PASSWORD=# à remplir

POSTGRES_DB=ma_base

DATABASE_URL=postgres://monapp:???@db:5432/ma_base

REDIS_URL=redis://redis:6379

NODE_ENV=development

.env dans .gitignore | .env.example versionné pour servir de template

Commandes essentielles

Le quotidien avec Docker Compose

Les commandes en détail

docker compose up -d # Lance tous les services en arrière-plan

docker compose down # Arrête et supprime conteneurs + réseau

docker compose logs -f # Suit les logs de tous les services

docker compose ps # Liste les conteneurs et leur état

docker compose exec backend sh # Ouvre un shell dans le conteneur

docker compose logs backend # Logs d'un service spécifique

up -d

Démarrer en arrière-plan

down

Arrêter + nettoyer

logs -f

Logs en temps réel

💡 docker compose down -v supprime aussi les volumes (attention aux données !)

Workflow quotidien typique

1️⃣

docker compose up -d

Je lance toute ma stack

2️⃣

docker compose logs -f

Je vérifie que tout démarre bien

3️⃣

docker compose ps

Je vérifie l'état des services (healthy ?)

4️⃣

docker compose exec backend sh

J'ouvre un shell pour debugguer

5️⃣

Ctrl+C puis docker compose down

Arrêt propre

Piège n°1 : docker-compose vs docker compose

docker-compose

❌ Ancienne version (v1)

Installation séparée (pip ou binaire)

Plus maintenu

docker compose

✅ Version moderne (v2)

Intégré à Docker Engine

Plugin officiel

📌 Toujours utiliser docker compose (avec un espace, pas de tiret)

Piège n°2 : depends_on sans health check

❌ Script de démarrage backend

console.log("Connexion à PostgreSQL...");

const client = new Client({ host: 'db' });

await client.connect(); // 💥 Crash !

// Error: connect ECONNREFUSED

Le conteneur db existe... mais PostgreSQL n'est pas encore prêt !

✅ Solution

# Dans docker-compose.yml

backend:

depends_on:

db:

condition: service_healthy

➕ healthcheck sur db avec pg_isready

Piège n°3 : localhost vs nom de service

❌ L'erreur classique

// Dans le backend

host: 'localhost'

port: 5432

// 💥 Connection refused !

Chaque conteneur a son propre localhost !

✅ La bonne pratique

// Dans le backend

host: 'db'

port: 5432

// ✅ Docker résout le nom du service

Les services communiquent par leur nom de service !

📌 La DATABASE_URL devient : postgres://user:pass@db:5432/mydb

Piège n°4 : mots de passe en dur dans le YAML

❌ À ne PAS faire

db:

environment:

POSTGRES_PASSWORD=supersecret

Mot de passe en clair, commité dans Git !

✅ La bonne pratique

# docker-compose.yml

db:

env_file:

- .env

# .env (dans .gitignore)

POSTGRES_PASSWORD=supersecret

Le .env est local, .env.example est versionné

Points clés à retenir

📄 Docker Compose remplace les longues commandes docker run

Fichier déclaratif versionnable, reproductible

⚠️ depends_on seul ne suffit pas

Toujours ajouter healthcheck + condition: service_healthy

🔗 Communication par nom de service

Les services se résolvent par leur nom, pas par localhost

🔐 Jamais de secrets en dur

Utiliser env_file et .env dans .gitignore

Récapitulatif

docker compose up -d

Lance toute la stack

docker compose down

Arrêt + nettoyage

depends_on

+ health check obligatoire

.env

Variables externalisées

📌 docker compose (sans tiret, v2 intégrée)

Commencez par copier un docker-compose.yml existant !

Exercices pratiques

1. Créer un docker-compose.yml pour une app Node + MongoDB

Avec health check sur MongoDB (mongo --eval "db.adminCommand('ping')")

2. Ajouter un service Redis avec dépendance conditionnelle

depends_on + condition: service_healthy

3. Externaliser les variables dans .env

Créer .env et .env.example, utiliser env_file dans le YAML

4. Tester le workflow complet

docker compose up -d, logs -f, ps, exec, down

Questions ?

Docker Compose simplifie radicalement vos stacks multi-conteneurs

Pratiquez sur vos projets existants !

Remplacer vos scripts shell par un docker-compose.yml