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 :
imagesélectionne une image existante, par exemple PostgreSQL ou Redis.buildconstruit une image depuis un Dockerfile local.portspublie un port du conteneur sur l’hôte.volumesconserve des données ou monte un fichier local.environmentdéfinit directement des variables d’environnement.env_fileinjecte les variables contenues dans un fichier.depends_ondécrit les dépendances entre services.networksconnecte un service à un ou plusieurs réseaux.restartdéfinit la politique de redémarrage.healthcheckvé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.