Lancer un conteneur isolé, c’est bien. Faire tourner une application complète avec sa base de données, son cache et son backend — le tout en une seule commande — c’est mieux. Docker Compose est l’outil qui transforme une série de docker run en une stack déclarative, versionnée et reproductible.
Dans ce chapitre, on va construire une application réaliste de bout en bout : une API Python (Flask), une base PostgreSQL et un cache Redis. Tout orchestré par un seul fichier docker-compose.yml.
Pourquoi Docker Compose ?#
Sans Compose, démarrer une stack multi-conteneurs implique :
- Créer manuellement un réseau Docker
- Lancer chaque conteneur avec les bons flags (
--network,--volume,-e,-p…) - Gérer l’ordre de démarrage à la main
- Reproduire tout ça sur chaque machine
Docker Compose résout ces problèmes avec un fichier YAML déclaratif. Vous décrivez l’état souhaité, Compose s’occupe du reste.
Avantages concrets :
- Un fichier = une stack — tout est versionné dans Git
- Une commande —
docker compose uplance tout - Isolation par projet — chaque stack a ses réseaux et volumes
- Reproductibilité — même comportement en dev, CI et staging
Docker Compose est inclus dans Docker Desktop et dans le CLI Docker récent (plugin
compose). La commande moderne estdocker compose(sans tiret). L’ancien binairedocker-composeest déprécié.
Anatomie d’un docker-compose.yml#
Un fichier Compose se structure autour de trois blocs principaux :
| |
Services#
Chaque service correspond à un conteneur. On y définit l’image (ou le build), les ports, les volumes, les variables d’environnement et les dépendances.
| |
Points clés :
- Le nom du service (
api) devient le hostname DNS interne — les autres conteneurs peuvent joindre ce service viahttp://api:5000 build: ./apiindique le répertoire contenant le Dockerfiledepends_oncontrôle l’ordre de démarrage (mais pas la disponibilité — on verra les healthchecks plus bas)
Networks#
Par défaut, Compose crée un réseau bridge pour chaque projet. Tous les services y sont connectés et peuvent communiquer entre eux par leur nom de service.
Vous pouvez définir des réseaux personnalisés pour isoler certains services :
| |
Ici, db n’est accessible que depuis le réseau backend. Un éventuel service frontend n’y aurait pas accès.
Volumes#
Les volumes persistent les données au-delà du cycle de vie des conteneurs. Deux syntaxes existent :
| |
- Volume nommé (
pg-data:) — stocké par Docker, performant, idéal pour les données persistantes - Bind mount (
./init.sql:) — lie un fichier/répertoire de l’hôte, pratique pour le développement
Projet complet : API + PostgreSQL + Redis#
Passons à la pratique. On va construire une stack réaliste avec :
- api — une application Flask qui expose des endpoints REST
- db — PostgreSQL pour le stockage persistant
- redis — Redis pour le cache applicatif
Structure du projet#
| |
Le Dockerfile de l’API#
| |
| |
L’application Flask#
| |
Le script d’initialisation SQL#
| |
Le fichier docker-compose.yml complet#
| |
Variables d’environnement et fichier .env#
Coder des mots de passe en dur dans docker-compose.yml est une mauvaise idée. Compose charge automatiquement un fichier .env situé à la racine du projet.
Le fichier .env#
| |
Règles importantes :
- Le fichier
.envest chargé automatiquement par Compose - Ajoutez
.envà votre.gitignore— ne commitez jamais de secrets - Fournissez un
.env.exampleavec des valeurs fictives pour documenter les variables attendues - La syntaxe
${VARIABLE:-default}permet de définir une valeur par défaut
Passer des variables aux conteneurs#
Trois méthodes, par ordre de priorité :
| |
Profiles : activer des services à la demande#
Les profiles permettent de regrouper des services optionnels qui ne démarrent pas par défaut.
| |
| |
Les services avec un profiles: ne démarrent que si le profil est explicitement activé. Pratique pour séparer les outils de développement du runtime de production.
Healthchecks et depends_on#
Le problème#
depends_on sans condition garantit uniquement que le conteneur est démarré, pas que le service est prêt. PostgreSQL peut prendre plusieurs secondes à accepter des connexions après le démarrage du conteneur.
La solution : healthchecks#
Un healthcheck est une commande exécutée périodiquement dans le conteneur pour vérifier que le service est fonctionnel :
| |
Combiné avec depends_on conditionnel :
| |
Avec cette configuration, le conteneur api ne démarre qu’une fois que db et redis sont en état healthy. Plus de crash au démarrage parce que la base n’est pas encore prête.
Vérifier l’état des healthchecks#
| |
La colonne STATUS affiche healthy, unhealthy ou starting pour les services avec un healthcheck configuré.
Commandes essentielles#
Démarrer la stack#
| |
Arrêter et nettoyer#
| |
Consulter les logs#
| |
Exécuter des commandes dans un conteneur#
| |
Construire et reconstruire#
| |
Autres commandes utiles#
| |
Bonnes pratiques#
1. Un service = une responsabilité#
Chaque service fait une seule chose. Ne mettez pas votre app et votre base dans le même conteneur.
2. Toujours des healthchecks#
Pour tout service dont d’autres dépendent, définissez un healthcheck. C’est la seule façon fiable de gérer l’ordre de démarrage.
3. Images avec tag explicite#
| |
Un tag explicite garantit la reproductibilité. latest peut changer à tout moment et casser votre stack.
4. Restart policies#
| |
En développement, unless-stopped est un bon défaut. En production avec un orchestrateur, no est souvent préférable (laissez l’orchestrateur gérer).
5. Limiter les ressources#
| |
Empêche un conteneur de consommer toutes les ressources de l’hôte.
Exercices pratiques#
Exercice 1 — Lancer la stack#
- Créez la structure du projet décrite dans cet article
- Lancez la stack avec
docker compose up -d --build - Vérifiez que tous les services sont
healthyavecdocker compose ps - Testez l’endpoint
http://localhost:5000/— le compteur de visites doit s’incrémenter - Testez le healthcheck :
http://localhost:5000/health
Exercice 2 — Explorer et débugger#
- Consultez les logs de l’API :
docker compose logs -f api - Ouvrez un shell dans le conteneur PostgreSQL et listez les tables :
1docker compose exec db psql -U devops -d appdb -c '\dt' - Vérifiez le compteur Redis :
1docker compose exec redis redis-cli GET visits - Modifiez le code de
app.py, puis relancez uniquement l’API :1docker compose up -d --build api
Exercice 3 — Ajouter un service#
Ajoutez Adminer (interface web pour gérer PostgreSQL) à la stack :
- Ajoutez un service
admineravec le profiledebug - Exposez le port 8080
- Connectez-le au même réseau
- Lancez avec
docker compose --profile debug up -d - Accédez à
http://localhost:8080et connectez-vous à la base
Exercice 4 — Sécuriser la configuration#
- Déplacez tous les secrets dans le fichier
.env - Créez un
.env.exampleavec des valeurs fictives - Vérifiez que la configuration est correctement résolue :
1docker compose config - Ajoutez
.envà.gitignore
Exercice 5 — Simuler une panne#
- Arrêtez le conteneur Redis manuellement :
1docker compose stop redis - Appelez
/health— le status doit êtreunhealthy - Observez le comportement de la politique de restart :
1docker compose ps - Relancez Redis :
docker compose start redis - Vérifiez que le healthcheck repasse à
healthy
Résumé#
Docker Compose transforme une collection de conteneurs en une application cohérente et reproductible. Les concepts clés de ce chapitre :
| Concept | Rôle |
|---|---|
services | Définir les conteneurs et leur configuration |
networks | Isoler la communication entre services |
volumes | Persister les données |
.env | Externaliser la configuration sensible |
profiles | Activer des services à la demande |
healthcheck | Vérifier la disponibilité réelle d’un service |
depends_on + condition | Orchestrer l’ordre de démarrage |
Dans le prochain chapitre, on abordera la construction d’images optimisées : multi-stage builds, layer caching, sécurité et bonnes pratiques pour des images de production légères et sûres.