Skip to Content
ConfigDocker Compose

Docker Compose — Guide pratique

Orchestrer des stacks multi-containers avec docker compose (v2).

docker compose est la sous-commande officielle (avec espace, pas de tiret). L’ancien binaire docker-compose reste installé sur certaines machines mais est déprécié depuis 2021.

Structure d’un fichier

Une stack Compose se compose de clés de niveau supérieur :

CléRôle
servicesDéfinition des containers (le cœur)
networksRéseaux partagés entre services
volumesVolumes nommés persistants
configsFichiers de config injectés comme données
secretsSecrets injectés en mémoire (/run/secrets)

Le format YAML est sensible à l’indentation : 2 espaces recommandées.

Services

Définition de base d’un service :

services: app: image: node:20-alpine command: ["node", "server.js"] working_dir: /app user: "1000:1000" env_file: .env environment: - NODE_ENV=production - PORT=3000 depends_on: db: condition: service_healthy restart: unless-stopped

command: écrase le CMD du Dockerfile. entrypoint: le remplace entièrement. Les deux peuvent coexister : la command sert d’arguments à l’entrypoint.

Dépendances conditionnelles

depends_on accepte un contexte de santé pour éviter les démarrages prématurés :

services: app: depends_on: db: condition: service_healthy cache: condition: service_started db: healthcheck: test: ["CMD", "pg_isready", "-U", "postgres"] interval: 10s timeout: 5s retries: 5 cache: healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 3

Healthchecks

Chaque service peut avoir un healthcheck — c’est la base d’une stack résiliente :

services: web: healthcheck: test: ["CMD", "curl", "-f", "http://localhost:80/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s
ChampUsage
testCommande ; exit 0 = sain
intervalDélai entre deux tests
timeoutDélai max par test
retriesÉchecs avant passage à unhealthy
start_periodPériode de grâce après le démarrage

Volumes

services: db: volumes: # Bind mount (hôte → conteneur) - ./data:/var/lib/postgresql/data # Volume nommé (géré par Docker) - postgres_data:/var/lib/data/other # Chemin hôte absolu - /tmp/logs:/var/log/app:ro # Fichier uniquement - ./conf.d/app.conf:/etc/app/config.conf:ro volumes: postgres_data: driver: local driver_opts: type: none o: bind device: /mnt/backups/postgres

Les bind mounts (./) reflètent les modifications en temps réel — idéal pour le développement. Les volumes nommés persistent au-delà de docker compose down.

Réseaux

services: frontend: networks: - public - backend backend: networks: - backend db: networks: - backend networks: public: driver: bridge backend: internal: true # pas d'accès depuis l'hôte driver: bridge
OptionEffet
internal: truePas de route sortante depuis le réseau
driver: bridgeValeur par défaut, isolation par conteneur
driver: hostPas de network namespace dédié
attachable: trueRéseau accessible par docker run --network

Résolution DNS interne

Les containers d’une même stack se résolvent entre eux par leur nom de service :

# Dans une Stack, ces services se pingent via "db", "cache", etc. # Pas besoin d'IP ni de variables d'environnement réseau. environment: DATABASE_URL: postgres://user:***@db:5432/appdb

Le DNS interne fonctionne uniquement au sein d’un réseau Compose. Les réseaux créés à la main (docker network create) ne sont pas automatiquement reliés.

Variables d’environnement

Trois méthodes pour injecter des vars dans les services :

services: app: # 1. Inline (lisible) environment: PORT: 3000 # 2. Fichier externe (recommandé pour les secrets) env_file: - .env - .env.local # 3. Interpolation depuis l'environnement de l'hôte environment: - DB_HOST=${DB_HOST:-db} - DB_PORT=${DB_PORT:-5432}

Format $VARIABLE et ${VARIABLE:-default} :

# Syntaxe Compose environment: HOST: "${APP_HOST:-localhost}" # valeur par défaut PORT: "${APP_PORT}" # si non défini → vide SECRET: "${APP_SECRET}" # erreur si absent

.env est chargé automatiquement par docker compose si présent dans le répertoire courant. Le nom par défaut peut être écrasé avec --env-file mon-fichier.env.

Templates courants

Node.js + PostgreSQL + Redis

services: app: build: . ports: - "3000:3000" env_file: .env depends_on: db: condition: service_healthy cache: condition: service_healthy restart: unless-stopped db: image: postgres:16-alpine volumes: - pg_data:/var/lib/postgresql/data environment: POSTGRES_USER: ${DB_USER} POSTGRES_PASSWORD: ${DB_PASS} POSTGRES_DB: ${DB_NAME} healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"] interval: 5s retries: 10 restart: unless-stopped cache: image: redis:7-alpine volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s retries: 5 restart: unless-stopped volumes: pg_data: redis_data:

Reverse proxy (Nginx)

services: nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx/ssl:/etc/nginx/ssl:ro - certbot-logs:/var/log/letsencrypt restart: unless-stopped certbot: image: certbot/certbot volumes: - ./nginx/ssl:/etc/letsencrypt - certbot-logs:/var/log/letsencrypt entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" volumes: certbot-logs:

Stack LAMP basique

services: web: image: httpd:2.4-alpine ports: - "8080:80" volumes: - ./html:/usr/local/apache2/htdocs:ro - ./conf/httpd.conf:/usr/local/apache2/conf/httpd.conf:ro php: image: php:8.2-apache volumes: - ./html:/var/www/html:ro db: image: mariadb:11 environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: app MYSQL_USER: appuser MYSQL_PASSWORD: apppass volumes: - mysql_data:/var/lib/mysql restart: unless-stopped volumes: mysql_data:

Debugging & maintenance

# Lister les services docker compose ps # Voir les logs (tous les services) docker compose logs -f # Logs d'un seul service docker compose logs -f app # Reconstruire et relancer (force rebuild) docker compose up -d --build # Reconstruire sans cache docker compose build --no-cache # Lire la configuration résolue (utile pour debug interpolation) docker compose config # Lancer un service ponctuel (non défini dans la stack) docker compose run --rm app npm test # Nettoyer (containers, réseaux) — pas les volumes ! docker compose down # Nettoyer aussi les volumes docker compose down -v # Arrêter sans supprimer docker compose pause docker compose unpause

Pièges fréquents

  • Ports doublés : deux services tentent d’exposer le même port hôte. Vérifier avec docker compose ps et ajuster les mappings "8081:80".

  • Nom de réseau par défaut : Compose crée un réseau <dossier>_default automatiquement. Le nom peut varier si le dossier contient des caractères spéciaux ou des majuscules. Préciser networks: explicitement pour un contrôle total.

  • depends_on ne vérifie pas la santé par défaut : sans condition: service_healthy, le service dépendant démarre dès que le service parent est créé — pas quand il est prêt.

  • Chemin relatif dans volumes : les bind mounts sont résolus relativement au fichier docker-compose.yml, jamais au cwd. ./data suppose que le fichier est à la racine du projet.

  • Variables non interpolées dans les secrets : dans environment, les variables ${VAR} ne sont interpolées que depuis le fichier .env et l’environnement d’exécution. À l’intérieur de command: ou args:, utiliser $$ pour échapper (ex. : $${VAR}) et éviter l’interpolation Compose.

  • Rebuild silencieux : modifier un fichier copié via COPY dans un Dockerfile ne déclenche pas un rebuild automatique. Lancer docker compose build explicitement.

  • .env et fichiers sensibles : .env est ignoré par docker compose pour ARG dans les Dockerfile. Utiliser --build-arg en ligne de commande pour les secrets de build.

  • Container morts en exited : si un service se fait SIGTERM puis redémarre en boucle, vérifier les logs avec docker compose logs -f --tail 50 nom-service. Souvent un healthcheck qui échoue ou un secret manquant.


Référence : Documentation officielle Docker Compose . Tester toute stack avec docker compose config avant de la déployer — c’est l’équivalent d’un docker run --validate.