Intermédiaire ⏱ 25 min de mise en œuvre Mis à jour le 11 août 2026

Orchestrer plusieurs services avec Docker Compose

Une app réelle, c'est plusieurs services : app, base, cache, proxy. Docker Compose décrit tout dans un fichier versionnable. Voici le guide de référence, de l'anatomie à la production.

Une application réelle ne se limite presque jamais à un seul processus. Elle associe généralement une application web, une base de données, un cache et parfois un reverse proxy chargé de recevoir les requêtes HTTP. Installer, configurer et démarrer chaque composant manuellement devient vite fragile, surtout lorsque plusieurs développeurs ou environnements sont concernés.

Docker Compose décrit cet ensemble dans un fichier déclaratif et versionnable. Il définit les conteneurs, leurs réseaux, leurs volumes, leurs variables et leurs dépendances. Une seule commande permet ensuite de créer une pile reproductible sur un poste de développement, un serveur de test ou une future instance VPS TalCloud hébergée en France.

Anatomie d’un fichier compose.yaml

Un fichier compose.yaml contient principalement une section services. Chaque service représente un conteneur de l’application.

Les propriétés les plus courantes sont les suivantes :

  • image sélectionne une image existante, par exemple PostgreSQL ou Redis.
  • build construit une image depuis un Dockerfile local.
  • ports publie un port du conteneur sur l’hôte.
  • volumes conserve des données ou monte un fichier local.
  • environment définit directement des variables d’environnement.
  • env_file injecte les variables contenues dans un fichier.
  • depends_on décrit les dépendances entre services.
  • networks connecte un service à un ou plusieurs réseaux.
  • restart définit la politique de redémarrage.
  • healthcheck vérifie qu’un service est réellement opérationnel.

Compose utilise YAML. L’indentation est donc significative et les tabulations doivent être évitées.

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "127.0.0.1:8000:8000"
    restart: unless-stopped

La forme 127.0.0.1:8000:8000 rend le service accessible uniquement depuis la machine hôte. Sans l’adresse 127.0.0.1, Docker peut publier le port sur toutes les interfaces réseau. Le service build fait référence à un Dockerfile présent dans le projet.

Exemple complet : application, PostgreSQL, Redis et proxy

Cette pile comprend une application construite localement, PostgreSQL, Redis et Nginx. Deux réseaux séparent le trafic frontal du réseau de données. PostgreSQL et Redis ne publient aucun port sur l’hôte.

services:
  proxy:
    image: nginx:1.27.5-alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      app:
        condition: service_healthy
    networks:
      - frontend
    restart: unless-stopped

  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "127.0.0.1:${APP_PORT:-8000}:8000"
    env_file:
      - .env
    environment:
      DATABASE_HOST: db
      DATABASE_PORT: "5432"
      REDIS_HOST: cache
      REDIS_PORT: "6379"
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 20s
    networks:
      - frontend
      - backend
    restart: unless-stopped

  db:
    image: postgres:17.4-alpine
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - backend
    restart: unless-stopped

  cache:
    image: redis:7.4.2-alpine
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5
    networks:
      - backend
    restart: unless-stopped

networks:
  frontend:
  backend:
    internal: true

volumes:
  postgres_data:
  redis_data:

Le contrôle de santé de l’application suppose que l’image construite contient curl et expose une route /health. Il faut adapter cette commande au langage et aux outils présents dans l’image.

Le fichier nginx.conf peut rester minimal :

server {
    listen 80;

    location / {
        proxy_pass http://app:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Nginx contacte app par son nom de service. L’application utilise de la même manière db et cache.

Comprendre les réseaux Docker Compose

Sans configuration explicite, Compose crée un réseau par défaut et y connecte tous les services. Chaque conteneur dispose alors d’une résolution DNS interne basée sur les noms de services.

Depuis le conteneur app, PostgreSQL est donc accessible à l’adresse db:5432. Utiliser localhost:5432 serait incorrect : dans un conteneur, localhost désigne ce conteneur lui-même.

Créer plusieurs réseaux limite les communications inutiles. Dans l’exemple, le proxy partage frontend avec l’application, tandis que PostgreSQL et Redis restent sur backend. La propriété internal: true rend ce réseau interne à Docker. La base et le cache n’ont par ailleurs aucune section ports, ce qui évite une exposition publique accidentelle.

Choisir et gérer les volumes

Les volumes nommés sont gérés par Docker. Ils conviennent particulièrement aux données de PostgreSQL et Redis :

volumes:
  - postgres_data:/var/lib/postgresql/data

Un redémarrage ou une recréation du conteneur ne supprime pas ce volume. Les données restent donc disponibles après un docker compose down classique.

Les bind mounts relient directement un chemin de l’hôte à un chemin du conteneur :

volumes:
  - ./src:/app/src

Ils sont pratiques en développement pour voir immédiatement les modifications du code. En production, ils augmentent le couplage avec l’arborescence du serveur et doivent être utilisés avec prudence.

Attention : la commande docker compose down -v supprime les volumes nommés de la pile. Pour une base de données, cela signifie généralement une perte de données si aucune sauvegarde n’existe.

Variables d’environnement et fichier .env

Compose charge automatiquement un fichier .env situé près du fichier Compose pour résoudre les expressions ${VAR}. Cette interpolation ne signifie pas que toutes les variables sont injectées dans les conteneurs. Pour cela, il faut utiliser environment ou env_file.

Exemple de fichier .env local :

APP_PORT=8000
POSTGRES_DB=talcloud_app
POSTGRES_USER=talcloud
POSTGRES_PASSWORD=change-me
APP_SECRET=change-me-too

Le fichier .env contient souvent des secrets. Il ne doit pas être versionné. Ajoutez-le à .gitignore et fournissez plutôt un .env.example avec les noms de variables et des valeurs factices :

APP_PORT=8000
POSTGRES_DB=app
POSTGRES_USER=app
POSTGRES_PASSWORD=replace-me
APP_SECRET=replace-me

En production, les secrets devraient être séparés de la configuration ordinaire et fournis par un gestionnaire de secrets, des fichiers protégés ou le système de déploiement, comme le détaille notre guide sur la gestion des secrets. Une variable d’environnement reste visible par les processus et outils ayant accès au conteneur.

depends_on et healthchecks

depends_on sans condition contrôle l’ordre de création des conteneurs, mais ne garantit pas qu’un service soit prêt. PostgreSQL peut être démarré tout en poursuivant son initialisation.

La condition service_healthy demande à Compose d’attendre la réussite du healthcheck :

depends_on:
  db:
    condition: service_healthy

Cette protection améliore les démarrages, mais elle ne remplace pas la résilience applicative. Une base peut redémarrer ou devenir momentanément indisponible après le lancement. L’application doit donc gérer les délais, les erreurs temporaires et les tentatives de reconnexion avec une attente progressive.

Commandes Docker Compose essentielles

Créer ou reconstruire les services, puis les lancer en arrière-plan :

docker compose up -d --build

Afficher leur état et leurs contrôles de santé :

docker compose ps

Suivre les journaux de toute la pile ou d’un service :

docker compose logs -f
docker compose logs -f app

Ouvrir un shell ou exécuter une commande dans un conteneur :

docker compose exec app sh
docker compose exec db psql -U talcloud -d talcloud_app

Redémarrer un service :

docker compose restart app

Arrêter et supprimer les conteneurs et réseaux de la pile :

docker compose down

Supprimer également les volumes, uniquement si la destruction des données est voulue :

docker compose down -v

Télécharger les nouvelles images référencées :

docker compose pull

Profils et fichiers de surcharge

Les profils permettent d’activer des services optionnels, comme un outil d’administration réservé au développement :

services:
  adminer:
    image: adminer:5.2.1
    profiles:
      - debug
    ports:
      - "127.0.0.1:8081:8080"

Le profil s’active explicitement :

docker compose --profile debug up -d

Une autre approche consiste à conserver un fichier de base, puis une surcharge adaptée à chaque environnement :

docker compose -f compose.yaml -f compose.dev.yaml up -d
docker compose -f compose.yaml -f compose.prod.yaml up -d

Le fichier de développement peut ajouter des bind mounts et publier le port de l’application. Le fichier de production peut retirer ce port direct, ajuster les ressources et imposer une configuration plus stricte. La configuration fusionnée peut être vérifiée avant le déploiement :

docker compose -f compose.yaml -f compose.prod.yaml config

Bonnes pratiques pour la production

Épinglez les versions des images au lieu d’utiliser latest. Une mise à jour doit être volontaire, testée et réversible. Pour une reproductibilité renforcée, une image peut aussi être référencée par son digest.

Ne publiez jamais les ports de PostgreSQL ou Redis sans nécessité précise. Utilisez des réseaux dédiés, des contrôles de santé et restart: unless-stopped. Appliquez les migrations de base de données de manière contrôlée et évitez de les lancer simultanément depuis plusieurs réplicas.

Séparez les secrets du dépôt Git, limitez les droits des fichiers et exécutez les conteneurs avec un utilisateur non privilégié lorsque l’image le permet. Les journaux doivent être surveillés et leur rotation configurée pour éviter de remplir le disque.

Sur une infrastructure TalCloud, les données peuvent rester hébergées sur des serveurs en France, dans un cadre adapté aux exigences RGPD. Le stockage NVMe répond aux charges nécessitant des entrées-sorties rapides, tandis que la protection Anti-DDoS Netrix et le support humain complètent l’exploitation. Les offres VPS démarrent à 4,99 € par mois et doivent être dimensionnées selon la mémoire, le stockage et la charge des services.

Mise à jour, sauvegarde et exploitation

Une mise à jour courante consiste à récupérer les images épinglées disponibles, puis à recréer uniquement les conteneurs concernés :

docker compose pull
docker compose up -d
docker compose ps
docker compose logs --since=10m

Compose conserve les volumes lors de cette recréation. Cela ne dispense pas de sauvegardes régulières. Pour PostgreSQL, privilégiez une sauvegarde cohérente avec pg_dump ou une stratégie physique adaptée, puis testez réellement la restauration. Notre guide sur les sauvegardes chiffrées avec restic aide à automatiser ces exports.

Avant une mise à jour importante, sauvegardez les données, vérifiez les notes de migration des images et préparez une procédure de retour arrière. Docker Compose simplifie l’orchestration d’une pile sur un serveur unique, mais la fiabilité dépend toujours de contrôles de santé pertinents, de sauvegardes restaurables et d’une supervision active.