Docker Compose : orchestrer les services du LABO
Nous avons maintenant Docker pour exécuter nos conteneurs et Traefik pour organiser leur accès depuis Internet. Mais une question apparaît rapidement : comment allons-nous gérer plusieurs conteneurs qui doivent fonctionner ensemble ? C'est précisément le rôle de Docker Compose. Il permet de décrire les services, leurs réseaux, leurs volumes et leurs paramètres dans un même fichier de configuration.
1. Pourquoi Docker Compose ?
Avec Docker seul, nous pouvons créer et démarrer des conteneurs individuellement. Cela fonctionne très bien pour un service isolé, mais devient rapidement difficile à maintenir lorsque plusieurs composants doivent fonctionner ensemble. Dans notre LABO, Traefik, n8n, PostgreSQL, Qdrant ou encore AnythingLLM constituent autant de services différents. Compose nous permet de décrire cette architecture dans un fichier unique, puis de demander à Docker de créer et démarrer l'ensemble.
Docker exécute les conteneurs. Docker Compose décrit comment ces conteneurs doivent fonctionner ensemble.
2. Le fichier Compose
Docker Compose utilise un fichier YAML, généralement nommé
compose.yaml.
Ce fichier devient la description déclarative de notre application.
Nous y définissons notamment les services, les réseaux et les volumes.
La structure minimale d'un fichier Compose commence par la section
services.
services:
service1:
image: exemple/image
service2:
image: autre/image
Chaque entrée sous services représente un service de notre
application.
3. Un service n'est pas simplement un conteneur
Dans Compose, le terme service désigne la définition d'un composant de l'application. Cette définition indique notamment quelle image utiliser et comment le conteneur doit être exécuté.
Ici, n8n est le nom logique du service et
image indique l'image Docker utilisée.
services:
n8n:
image: n8nio/n8n:latest
Le nom du service devient également un élément important pour la communication entre conteneurs sur un réseau Docker.
4. Image, configuration et exécution
Le fichier Compose ne contient généralement pas le logiciel lui-même. Il décrit comment Docker doit utiliser une image existante, ou éventuellement comment construire une image.
Un service peut par exemple être basé directement sur une image publiée dans un registre Docker.
services:
web:
image: nginx:alpine
Compose ajoute ensuite autour de cette image les éléments nécessaires : réseaux, volumes, variables d'environnement, ports, labels ou dépendances.
5. Les ports : publier ou ne pas publier
Nous avons déjà rencontré cette distinction avec Traefik. Un conteneur peut écouter sur un port interne sans que ce port soit publié directement sur le serveur. Compose permet néanmoins de publier explicitement un port lorsque cela est nécessaire.
Cette configuration publie le port 8080 du serveur vers le port 80 du conteneur.
services:
web:
image: nginx:alpine
ports:
- "8080:80"
Dans notre architecture finale, Traefik sera généralement le service qui publie les ports HTTP et HTTPS vers l'extérieur. Les autres services pourront rester accessibles uniquement à travers les réseaux Docker appropriés.
6. Les réseaux
Les services doivent pouvoir communiquer entre eux lorsqu'ils constituent
une même application.
Compose crée par défaut un réseau pour le projet, mais nous pouvons également
définir nos propres réseaux.
C'est exactement ce que nous avons commencé à faire avec le réseau
proxy utilisé par Traefik.
Nous pouvons rattacher explicitement un service au réseau
proxy.
services:
n8n:
image: n8nio/n8n:latest
networks:
- proxy
networks:
proxy:
external: true
Le mot-clé external indique ici que le réseau existe déjà
en dehors du projet Compose et que Compose ne doit pas le créer.
7. Pourquoi plusieurs réseaux peuvent être utiles
Tous les services n'ont pas nécessairement besoin de communiquer avec tous les autres. Nous pouvons donc utiliser plusieurs réseaux afin de séparer les communications.
proxy.
proxy et son réseau interne.
proxy.
Les réseaux deviennent ainsi un outil d'organisation et d'isolation des communications entre services.
8. Les volumes : ne pas perdre les données
Un conteneur peut être supprimé puis recréé. Les données importantes d'une application ne doivent donc pas dépendre uniquement du système de fichiers interne du conteneur. Docker Compose permet d'associer des volumes aux services.
Exemple avec un volume nommé destiné à conserver les données d'une base PostgreSQL.
services:
postgres:
image: postgres:18
volumes:
- postgres-data:/var/lib/postgresql/data
volumes:
postgres-data:
Le conteneur peut ainsi être recréé sans que les données du volume soient automatiquement supprimées avec lui.
Le conteneur représente le service. Le volume représente les données qui doivent survivre au cycle de vie du conteneur.
9. Les variables d'environnement
De nombreux services doivent recevoir des paramètres au démarrage : nom d'utilisateur, mot de passe, nom de base de données, URL ou clé de configuration. Compose permet de transmettre ces valeurs au conteneur à l'aide de variables d'environnement.
Voici une forme simple de déclaration.
services:
application:
image: exemple/application
environment:
APP_MODE: production
APP_PORT: "8080"
Pour les valeurs sensibles, nous éviterons cependant de placer directement les secrets dans le fichier Compose versionné. Nous verrons plus loin comment organiser proprement cette configuration.
10. Le fichier .env
Compose peut utiliser un fichier .env pour fournir des valeurs
utilisées lors de l'interpolation de la configuration.
Le fichier peut par exemple contenir une variable destinée à définir un nom de domaine.
DOMAIN=n8n.exemple.be
Cette variable peut ensuite être utilisée dans le fichier Compose.
Compose remplacera la variable lors de l'interprétation du fichier.
labels:
- "traefik.http.routers.n8n.rule=Host(`${DOMAIN}`)"
Cette séparation entre configuration et valeurs variables deviendra particulièrement intéressante lorsque notre architecture commencera à contenir plusieurs services.
11. Les dépendances entre services
Certains services dépendent du démarrage d'autres services.
Une application peut par exemple avoir besoin d'une base de données.
Compose permet de déclarer cette relation avec
depends_on.
Ici, Compose sait que le service database constitue
une dépendance du service application.
services:
application:
image: exemple/application
depends_on:
- database
database:
image: postgres:18
Il faut toutefois distinguer l'ordre de démarrage de la disponibilité réelle d'un service. Un conteneur peut être démarré alors que l'application qu'il contient n'est pas encore prête à accepter des connexions.
Compose permet donc également d'utiliser des healthchecks et des conditions de dépendance adaptées lorsque cette distinction est importante.
12. Les labels : le lien avec Traefik
C'est ici que Docker Compose rejoint directement ce que nous avons construit dans l'article précédent. Les labels peuvent être définis directement dans la configuration d'un service. Traefik peut alors les découvrir grâce à son provider Docker.
Un même service peut donc regrouper son image, son réseau et les informations nécessaires à son exposition par Traefik.
services:
n8n:
image: n8nio/n8n:latest
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.n8n.rule=Host(`n8n.exemple.be`)"
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
networks:
proxy:
external: true
Le fichier Compose commence ainsi à devenir la véritable description opérationnelle de notre service.
13. Une architecture complète commence à apparaître
Nous pouvons maintenant réunir les notions découvertes dans les deux articles précédents.
Nous ne lançons donc plus simplement des conteneurs isolés. Nous commençons à construire une véritable architecture de services.
14. Une première stack Compose
Prenons maintenant une petite stack représentative de notre architecture : Traefik devant une application et une base de données derrière celle-ci.
Cet exemple n'est pas encore notre configuration finale. Il sert uniquement à visualiser comment les différentes briques s'assemblent dans un fichier Compose.
services:
application:
image: exemple/application
networks:
- proxy
- backend
labels:
- "traefik.enable=true"
- "traefik.http.routers.application.rule=Host(`app.exemple.be`)"
- "traefik.http.services.application.loadbalancer.server.port=8080"
depends_on:
- database
database:
image: postgres:18
networks:
- backend
volumes:
- database-data:/var/lib/postgresql/data
networks:
proxy:
external: true
backend:
volumes:
database-data:
Remarquons la séparation :
l'application est connectée aux réseaux proxy et
backend, tandis que la base de données n'est connectée
qu'au réseau interne backend.
La base de données n'a donc aucune raison d'être directement accessible
depuis Traefik.
15. Vérifier avant de démarrer
Un fichier Compose est une configuration. Avant de démarrer toute la stack, il est utile de vérifier que Compose peut correctement l'interpréter.
La commande docker compose config permet à Compose
d'analyser et de produire le modèle de configuration qui sera appliqué.
docker compose config
Cette étape est particulièrement intéressante lorsque nous utilisons des variables, plusieurs fichiers Compose ou des configurations plus complexes.
16. Démarrer et observer la stack
Une fois la configuration vérifiée, nous pouvons demander à Compose de démarrer les services.
La commande suivante crée et démarre les services définis dans le fichier Compose.
docker compose up -d
Nous pouvons ensuite vérifier l'état des services.
Cette commande affiche notamment les services et leur état.
docker compose ps
Les journaux peuvent également être suivis directement à travers Compose.
Nous pourrons ainsi observer simultanément les messages produits par les services de la stack.
docker compose logs -f
17. Le cycle de vie devient simple
L'un des intérêts fondamentaux de Compose apparaît lorsque nous devons arrêter, recréer ou mettre à jour une stack.
docker compose down
docker compose up -d
docker compose ps
docker compose logs -f
Nous disposons ainsi d'un ensemble cohérent de commandes permettant de gérer le cycle de vie de notre application.
Pourquoi Compose devient essentiel pour le LABO
À partir de maintenant, chaque nouveau composant du LABO pourra être décrit comme un service plutôt que comme une succession de commandes lancées manuellement.Nous pourrons ainsi définir séparément Traefik, n8n, PostgreSQL, Qdrant, AnythingLLM ou d'autres composants, tout en conservant une logique commune pour leur déploiement. Cette organisation prépare également une étape importante : pouvoir reconstruire une architecture complète à partir de fichiers de configuration reproductibles.
Docker fournit les briques. Compose décrit leur assemblage. Traefik organise leur accès depuis l'extérieur. Les réseaux et les volumes définissent leur environnement d'exécution.
À retenir
Docker Compose transforme notre collection de conteneurs en une architecture décrite et reproductible. Les services, réseaux, volumes, variables et dépendances sont regroupés dans une configuration que Docker peut interpréter et exécuter.



