Maintenance

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.

Étape 1. Traefik rejoint le réseau proxy.
Étape 2. n8n rejoint le réseau proxy et son réseau interne.
Étape 3. PostgreSQL rejoint uniquement le réseau interne nécessaire à n8n.
Étape 4. Un service qui n'a aucune raison d'être accessible par Traefik ne rejoint pas le réseau 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.

Étape 1. Docker fournit les conteneurs.
Étape 2. Docker Compose décrit les services et leur configuration.
Étape 3. Les réseaux déterminent quels services peuvent communiquer.
Étape 4. Les volumes conservent les données nécessaires.
Étape 5. Les labels décrivent à Traefik comment exposer certains services.
Étape 6. Traefik devient le point d'entrée HTTP/HTTPS du système.

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.

Arrêter. docker compose down
Démarrer. docker compose up -d
Observer. docker compose ps
Consulter les journaux. 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.

Table des matières

Les autres publications de cette section

0:00
0:00
Défiler vers le haut