Développement Web

Débuter avec Docker Compose : API et base de données

15 دقائق للقراءة

📍 Article principal du parcours : Docker de zéro : comprendre et utiliser les conteneurs
Troisième étape du parcours « Docker de zéro ». Elle suppose que vous avez déjà écrit votre premier Dockerfile pour l’API « Garde ».

Une application réelle, c’est rarement un seul conteneur. L’API « Garde » a besoin d’une base de données pour stocker les pharmacies. Lancer deux conteneurs à la main, avec deux longues commandes docker run, en les reliant correctement, devient vite pénible et source d’erreurs. Docker Compose résout exactement ce problème : un seul fichier compose.yaml décrit tous les services, leurs réglages et leurs liens, et une seule commande les démarre ensemble. Vous allez faire dialoguer votre API avec une vraie base PostgreSQL.

🎯 Ce que vous allez apprendre

  • écrire un fichier compose.yaml qui décrit plusieurs services ;
  • démarrer, arrêter et superviser toute une pile avec docker compose up et down ;
  • faire communiquer deux conteneurs par leur nom de service, sans publier la base sur Internet ;
  • ordonner le démarrage avec depends_on et un contrôle de santé (healthcheck) ;
  • passer la configuration par variables d’environnement.

🛠️ Ce que vous allez construire

La pile complète « Garde » : votre API Node, reliée à une base PostgreSQL qui contient réellement la liste des pharmacies. Le tout démarre d’une seule commande, et l’API lit ses données dans la base au lieu d’un tableau codé en dur.

Prérequis

  • L’image garde-api et son code du tutoriel précédent.
  • Docker Compose, intégré à Docker (vérifiez avec docker compose version).
  • Niveau débutant à intermédiaire. Test express : si vous avez construit l’image « Garde », vous êtes prêt.
  • ⏱️ Temps estimé : ~50 minutes.

Étape 1 — Préparer l’API à lire la base

Pour que l’API interroge PostgreSQL, on ajoute le client pg et on remplace le tableau codé en dur par une requête. La connexion se configure entièrement par variables d’environnement — c’est ce qui permettra à Compose de relier proprement les deux conteneurs.

// package.json : ajouter "pg" aux dependances
"dependencies": { "express": "^5.1.0", "pg": "^8.13.0" }
// server.js
import express from "express";
import pg from "pg";

const app = express();
const PORT = process.env.PORT || 3000;

const pool = new pg.Pool({
  host: process.env.DB_HOST || "localhost",
  user: process.env.DB_USER || "garde",
  password: process.env.DB_PASSWORD || "secret",
  database: process.env.DB_NAME || "garde",
  port: 5432
});

app.get("/pharmacies", async (req, res) => {
  try {
    const r = await pool.query("SELECT nom, quartier FROM pharmacies WHERE garde = true");
    res.json(r.rows);
  } catch (e) {
    res.status(500).json({ erreur: "base indisponible" });
  }
});

app.get("/sante", (req, res) => res.json({ statut: "ok" }));

app.listen(PORT, () => console.log(`API Garde a l'ecoute sur le port ${PORT}`));

Le point clé est le host : il sera localhost en dehors de Docker, mais Compose le fixera au nom du service de la base. L’API ne sait pas tourne la base ; elle se contente de la joindre par un nom logique. Reconstruisez l’image pour intégrer la nouvelle dépendance : docker build -t garde-api:2.0 .

Point d’étape — L’image garde-api:2.0 est construite avec pg dans ses dépendances. Elle ne fonctionnera pleinement qu’une fois la base branchée, à l’étape suivante.

Étape 2 — Préparer le jeu de données initial

La base démarre vide. L’image officielle PostgreSQL exécute automatiquement tout script .sql placé dans un répertoire spécial au premier démarrage. Créons un fichier init.sql qui crée la table et insère quelques pharmacies.

-- init.sql
CREATE TABLE pharmacies (
  id        SERIAL PRIMARY KEY,
  nom       TEXT NOT NULL,
  quartier  TEXT NOT NULL,
  garde     BOOLEAN NOT NULL DEFAULT false
);

INSERT INTO pharmacies (nom, quartier, garde) VALUES
  ('Pharmacie du Marche', 'Centre', true),
  ('Pharmacie de la Gare', 'Nord', true),
  ('Pharmacie de l''Avenue', 'Sud', false);

Ce script ne s’exécute qu’à la toute première initialisation de la base, quand son répertoire de données est vide. C’est suffisant pour amorcer « Garde » avec des données réalistes. En production, on gérerait plutôt le schéma avec des migrations, mais pour apprendre, ce mécanisme d’amorçage est idéal.

Point d’étape — Un fichier init.sql existe à la racine du dossier, à côté du Dockerfile.

Étape 3 — Écrire le fichier compose.yaml

Voici le cœur du tutoriel. Le fichier compose.yaml déclare deux services — api et db — avec leurs images, leurs variables et leurs liens. Lisez les commentaires : chaque bloc a un rôle précis.

services:
  api:
    build: .                  # construit l'image depuis le Dockerfile local
    ports:
      - "3000:3000"           # publie l'API sur l'hote
    environment:
      DB_HOST: db             # le nom du service de base = nom d'hote reseau
      DB_USER: garde
      DB_PASSWORD: secret
      DB_NAME: garde
    depends_on:
      db:
        condition: service_healthy   # attend que la base soit prete

  db:
    image: postgres:17
    environment:
      POSTGRES_USER: garde
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: garde
    volumes:
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql   # amorcage
      - db-data:/var/lib/postgresql/data                  # donnees persistantes
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U garde"]
      interval: 5s
      timeout: 3s
      retries: 5

volumes:
  db-data:

Plusieurs idées importantes tiennent dans ce fichier. Le service api est construit depuis votre Dockerfile (build: .), tandis que db utilise l’image officielle. La variable DB_HOST: db est la clé : Compose crée un réseau privé où chaque service est joignable par son nom. L’API atteint la base à l’adresse db, sans connaître son IP. Le bloc depends_on avec condition: service_healthy retarde le démarrage de l’API jusqu’à ce que le healthcheck de la base réponde — sans quoi l’API tenterait de se connecter à une base pas encore prête. Enfin, le volume ./init.sql monté dans le répertoire d’amorçage exécute notre script, et le volume nommé db-data conserve les données (sujet creusé au tutoriel suivant).

Point d’étape — Le fichier compose.yaml est à la racine, avec les fichiers Dockerfile, server.js, init.sql.

Étape 4 — Démarrer toute la pile

Le moment de vérité : une seule commande construit l’image de l’API, télécharge PostgreSQL, crée le réseau et les volumes, et démarre les deux conteneurs dans le bon ordre.

docker compose up -d --build

L’option -d lance en arrière-plan ; --build force la reconstruction de l’image de l’API pour prendre en compte vos derniers changements. Vous voyez Compose créer le réseau, le volume, puis démarrer db et attendre qu’elle soit saine avant de lancer api. Une fois la main rendue, docker compose ps liste les deux services « running ». Ouvrez http://localhost:3000/pharmacies : la liste provient désormais de PostgreSQL, pas d’un tableau en dur. L’API et la base tournent ensemble, reliées par leur réseau privé.

Point d’étapehttp://localhost:3000/pharmacies renvoie les pharmacies issues de la base. docker compose ps montre api et db en cours d’exécution.

Étape 5 — Superviser et lire les journaux

Compose offre les mêmes outils de diagnostic que Docker, mais à l’échelle de la pile entière. On peut suivre les journaux de tous les services à la fois, ou cibler l’un d’eux.

docker compose logs -f
docker compose logs api

docker compose logs -f affiche en continu (follow) les journaux mêlés des deux services, chacun préfixé par son nom — pratique pour voir l’API se connecter à la base au démarrage. docker compose logs api isole un service. Si l’API affichait « base indisponible », ces journaux révéleraient pourquoi : mauvais nom d’hôte, mauvais mot de passe, ou base pas encore prête. C’est votre premier réflexe en cas de souci.

Point d’étape — Les journaux de l’api montrent « API Garde à l’écoute », sans erreur de connexion à la base.

Étape 6 — Arrêter proprement

Pour arrêter et supprimer toute la pile, une seule commande suffit. Selon qu’on veut ou non conserver les données, on ajoute ou non une option.

docker compose down
# Pour tout supprimer, y compris le volume des donnees :
docker compose down -v

docker compose down arrête et retire les conteneurs et le réseau, mais conserve le volume db-data : vos pharmacies survivent, et un prochain up les retrouvera. Ajouter -v supprime aussi le volume — la base repartira alors vierge et rejouera init.sql. Cette distinction est cruciale : c’est elle qui sépare « je redémarre mon appli » de « je remets tout à zéro ». Le tutoriel sur les volumes y revient en détail.

Point d’étape — Après docker compose down puis docker compose up -d, les données sont toujours là. Après down -v, la base est réinitialisée.

Inspecter la base sans quitter Compose

Pendant le développement, on a souvent besoin de jeter un œil dans la base : vérifier qu’une donnée est bien là, lancer une requête à la main. Pas besoin d’installer un client PostgreSQL sur sa machine — la commande docker compose exec ouvre une session dans le conteneur de base, qui contient déjà tous les outils.

docker compose exec db psql -U garde -d garde -c "SELECT * FROM pharmacies;"

Cette commande exécute psql, le client en ligne de commande de PostgreSQL, à l’intérieur du conteneur db, et affiche le contenu de la table. Sans le -c, on obtiendrait une session interactive complète pour enchaîner les requêtes. C’est l’équivalent, pour Compose, du docker exec vu au premier tutoriel : on entre dans un conteneur en marche pour l’inspecter. Ce réflexe est précieux pour diagnostiquer — confirmer que init.sql a bien créé la table, ou qu’une insertion a réussi — sans rien installer côté hôte ni publier le port de la base. Tout reste dans le périmètre de Compose.

Notez au passage la cohérence du modèle : qu’il s’agisse de lire des journaux (logs), d’ouvrir un shell (exec) ou de lister l’état (ps), les commandes Compose reprennent celles de Docker, mais à l’échelle de la pile et en désignant les services par leur nom logique. Une fois ce vocabulaire acquis sur deux services, il s’applique sans changement à une application qui en compte dix.

Comprendre ce que Compose fait pour vous

Il vaut la peine de mesurer ce que cette unique commande remplace. Sans Compose, démarrer la même pile à la main exigerait de créer un réseau, de lancer la base avec une dizaine d’options, d’attendre qu’elle soit prête, puis de lancer l’API en lui passant la bonne adresse, les bons identifiants et le bon réseau — le tout dans le bon ordre, à refaire à chaque fois et à documenter quelque part. Compose transforme cette procédure fragile en un fichier déclaratif. On ne décrit plus une suite d’actions (« fais ceci, puis cela »), mais un état désiré (« voici les services que je veux, voici leurs liens »). Docker se charge de l’atteindre. C’est un changement de nature : le fichier compose.yaml est versionné avec le code, relu en revue, partagé sans ambiguïté. Il devient la source de vérité de l’architecture applicative.

Deux mécanismes invisibles méritent qu’on s’y arrête, car ils reviendront partout. Le premier est le réseau implicite : dès qu’on lance une pile, Compose crée un réseau privé et y attache tous les services. C’est lui qui permet à l’API de joindre la base par le simple nom db, sans IP ni configuration réseau manuelle. Chaque service devient un nom d’hôte résolu automatiquement. Le second est l’espace de noms du projet : Compose préfixe les conteneurs, réseaux et volumes par le nom du dossier. Cela permet de faire tourner plusieurs projets sur la même machine sans collision — deux piles « Garde » dans deux dossiers différents cohabitent sans se marcher dessus. Comprendre ces deux automatismes évite la confusion la plus fréquente du débutant, qui cherche pourquoi « ça marche » sans avoir rien configuré : c’est précisément que Compose l’a fait pour lui.

Enfin, gardez en tête que Compose ne se limite pas à deux services. Une application réelle peut en empiler beaucoup — une API, une base, un cache, une file de messages, un proxy — et le même fichier les orchestre tous. La logique reste identique : un bloc par service, des variables d’environnement pour la configuration, depends_on pour l’ordre, et le réseau privé qui les relie. Ce que vous venez d’apprendre sur deux conteneurs s’applique tel quel à dix.

🐞 Pièges fréquents

Symptôme / erreur Cause probable Correctif
L’API répond « base indisponible » Mauvais DB_HOST ou base pas prête Mettre DB_HOST: db (nom du service) et un depends_on avec healthcheck.
« connection refused » vers la base API démarrée avant la base Utiliser condition: service_healthy dans depends_on.
init.sql ignoré Le volume de données existait déjà Le script ne s’exécute qu’à l’initialisation ; faire down -v pour repartir à neuf.
« yaml: line … mapping values » Indentation incorrecte du YAML Indenter avec des espaces (jamais de tabulation), de façon cohérente.
Changement de code non pris en compte Image non reconstruite Relancer avec docker compose up -d --build.

Compose en équipe, sur un réseau modeste

Compose change concrètement la vie d’une petite équipe à Conakry ou Dakar. Un nouveau venu clone le dépôt, tape docker compose up, et obtient en quelques minutes exactement le même environnement que ses collègues — API et base comprises — sans passer une journée à installer PostgreSQL et à régler des conflits de versions. Le fichier compose.yaml devient la documentation vivante de la pile. Sur une connexion limitée, le bénéfice se double du cache : une fois postgres:17 et l’image Node tirées, les démarrages suivants sont quasi instantanés. Veillez seulement à ne pas publier le port de la base (5432) en production : ici, on ne publie que l’API. La base reste sur le réseau privé de Compose, invisible d’Internet — une sécurité gratuite que beaucoup oublient.

✅ Récapitulatif

Vous avez décrit une application multi-conteneurs dans un seul compose.yaml, démarré et arrêté la pile d’une commande, fait communiquer l’API et la base par nom de service sur un réseau privé, ordonné le démarrage avec un contrôle de santé, et compris la différence entre down et down -v. L’API « Garde » lit maintenant ses données dans une vraie base. Les deux mécanismes que Compose a introduits en filigrane — les volumes pour la persistance et le réseau pour la communication — méritent d’être maîtrisés pour eux-mêmes : c’est l’objet du prochain tutoriel.

🧾 Aide-mémoire

Commande Rôle
docker compose up -d Démarrer toute la pile en arrière-plan
docker compose up -d --build Reconstruire les images puis démarrer
docker compose ps Lister l’état des services
docker compose logs -f Suivre les journaux de tous les services
docker compose down Arrêter et supprimer conteneurs + réseau (garde les volumes)
docker compose down -v Idem + supprimer les volumes (données effacées)
docker compose exec db psql -U garde Ouvrir un client SQL dans le conteneur de base

💪 À vous de jouer

Ajoutez un troisième service : un adminer (interface web pour bases de données) qui permet d’inspecter PostgreSQL depuis le navigateur. Publiez-le sur le port 8081 et reliez-le à la base.

Voir une solution
  adminer:
    image: adminer:latest
    ports:
      - "8081:8080"
    depends_on:
      - db

Ajoutez ce bloc sous les autres services, relancez docker compose up -d, puis ouvrez http://localhost:8081. Connectez-vous avec le serveur db, l’utilisateur garde et son mot de passe. Vous naviguez dans la table pharmacies sans installer le moindre outil sur votre machine.

Tutoriels frères

Dans la continuité

Ressources et références

FAQ

Faut-il écrire docker compose ou docker-compose ?
La forme moderne est docker compose (avec un espace), intégrée à Docker en tant que sous-commande. L’ancienne commande docker-compose (avec un tiret), un outil Python séparé, est dépréciée. Utilisez la version avec espace.

Pourquoi ne pas publier le port de la base ?
Parce que l’API la joint par le réseau privé de Compose ; personne d’autre n’a besoin d’y accéder. Publier 5432 exposerait inutilement la base à l’extérieur. On ne publie que ce qui doit être public — ici, l’API.

À quoi sert le healthcheck exactement ?
Il indique à Docker comment vérifier qu’un service est réellement opérationnel, pas seulement « démarré ». Couplé à depends_on: condition: service_healthy, il garantit que l’API n’essaie de se connecter qu’une fois la base prête à accepter des connexions. Sans lui, on tombe souvent sur des erreurs de connexion au démarrage.

Le fichier doit-il s’appeler docker-compose.yml ou compose.yaml ?
Les deux fonctionnent. compose.yaml est le nom recommandé aujourd’hui par la spécification officielle ; docker-compose.yml reste reconnu pour la compatibilité. Choisissez compose.yaml pour les nouveaux projets.

مشاركة