infra / docker-compose
Configuration multi-environnements
Explication
Ce que vous allez apprendre
- Structurer un projet avec un fichier de base commun et des overlays par environnement
- Charger un fichier
.envspécifique à chaque environnement avec--env-file - Volontairement empêcher un déploiement si une variable critique n'a pas de valeur
- Combiner plusieurs fichiers Compose dans le bon ordre pour staging et production
- Encapsuler des commandes longues dans un Makefile pour réduire les erreurs humaines
Dans quel contexte ?
L'équipe de boutique-api déploie la même application sur trois environnements : son poste local, un serveur de staging pour les tests avant mise en production, et le serveur de production lui-même. Chacun a son propre nombre de replicas, ses propres limites mémoire et son propre tag d'image (API_TAG), construit à partir du SHA du commit Git. Cette leçon organise cette configuration sans jamais dupliquer le fichier de base.
Le problème : une application, plusieurs destinations
D'abord, un rappel : une application ne vit jamais dans un seul endroit. Elle est développée en local, testée en staging, puis déployée en production, et chacun de ces environnements a ses propres contraintes : ressources, réplicas, secrets, niveaux de log.
Le piège à éviter : dupliquer toute la config
Le premier réflexe serait de dupliquer l'intégralité de la configuration pour chaque environnement. Mais cela finit inévitablement par diverger, et crée des bugs du type "ça marchait en staging, pas en prod".
La stratégie : base commune + fichiers spécifiques
La bonne approche reprend tout ce qui a été vu dans les leçons précédentes : un fichier de base contient ce qui est partagé par tous les environnements, et des fichiers comme .staging.yml ou .prod.yml ajoutent ou modifient uniquement ce qui change réellement.
Ajouter un fichier .env par environnement
Une fois cette structure en place, il reste à gérer les valeurs sensibles ou spécifiques, comme les tags d'image ou les mots de passe. Chaque environnement reçoit son propre fichier .env pour cela, chargé explicitement avec --env-file.
Une règle de sécurité à ne jamais oublier
En production, certaines variables ne doivent JAMAIS avoir de valeur par défaut silencieuse. Un tag d'image sans valeur de secours force par exemple Compose à échouer explicitement si la variable n'est pas fournie, plutôt que de déployer accidentellement une mauvaise version.
| Environnement | Fichiers Compose combinés | Fichier .env |
|---|---|---|
| Local (dev) | docker-compose.yml + override.yml (auto) | .env |
| Staging | docker-compose.yml + docker-compose.staging.yml | .env.staging |
| Production | docker-compose.yml + docker-compose.prod.yml | .env.production |
Piège fréquent
Écrire image: "myorg/api:${API_TAG:-latest}" en production semble pratique, mais si API_TAG n'est pas défini par erreur dans le pipeline CI, la stack redéploie silencieusement l'image latest au lieu de la version attendue. En production, préfère image: "myorg/api:${API_TAG}" sans valeur par défaut : Compose refuse alors de démarrer plutôt que de se tromper de version.
Pourquoi cette rigidité volontaire est une bonne chose
Cela peut sembler contraignant, mais c'est voulu : mieux vaut un déploiement qui échoue immédiatement avec une erreur claire, qu'un déploiement silencieux avec la mauvaise image en production.
Un dernier conseil pratique
Enfin, évite de multiplier les commandes -f fichier1 -f fichier2 --env-file ... tapées à la main : les encapsuler dans un Makefile ou un script réduit fortement le risque d'erreur humaine au moment d'un déploiement, surtout sous pression.
Après cette organisation multi-environnements, la dernière étape de ce parcours "essentiel" consiste à durcir cette configuration pour qu'elle soit réellement prête pour la production, avec des bonnes pratiques de sécurité et de résilience.
Commandes & code
Configuration multi-environnements
Sur un projet réel, on gère souvent : local, staging, production, chacun avec ses propres images, secrets et ressources.
project/
├── docker-compose.yml # base commune (services, réseaux)
├── docker-compose.override.yml # dev local (auto-chargé)
├── docker-compose.staging.yml # config staging
├── docker-compose.prod.yml # config prod
├── .env # défauts partagés (non sensibles)
├── .env.staging
└── .env.production# docker-compose.yml - base
services:
api:
image: "myorg/api:${API_TAG:-latest}"
environment:
NODE_ENV: "${NODE_ENV:-development}"
networks:
- app_net
networks:
app_net:# docker-compose.prod.yml
services:
api:
image: "myorg/api:${API_TAG}" # pas de défaut -> échoue si non défini, volontairement
restart: always
deploy:
replicas: 3
resources:
limits:
memory: 512M
logging:
driver: json-file
options:
max-size: "20m"# Staging : combinaison base + staging, avec son propre fichier .env
docker compose --env-file .env.staging \
-f docker-compose.yml -f docker-compose.staging.yml \
up -d
# Production : API_TAG doit être défini (ex: le SHA du commit buildé en CI)
API_TAG=$(git rev-parse --short HEAD) \
docker compose --env-file .env.production \
-f docker-compose.yml -f docker-compose.prod.yml \
up -d --remove-orphans# Makefile - encapsule les commandes longues pour l'équipe
.PHONY: dev staging prod
dev:
docker compose up -d
staging:
docker compose --env-file .env.staging -f docker-compose.yml -f docker-compose.staging.yml up -d
prod:
docker compose --env-file .env.production -f docker-compose.yml -f docker-compose.prod.yml up -d --remove-orphansRésumé
- Un fichier de base + des overlays par environnement évite la duplication.
--env-filesélectionne les variables d'interpolation propres à chaque environnement.- Ne jamais donner de défaut à une variable sensible/critique en production (
API_TAGsans:-). - Encapsuler les commandes longues (Makefile, scripts) réduit les erreurs humaines.
Exercices pratiques
Mission : la mauvaise version déployée en silence
Objectif : Corriger une valeur par défaut dangereuse sur une variable critique de production.
Contexte
En production, image: "myorg/api:${API_TAG:-latest}" a redéployé silencieusement l'image latest au lieu de la version attendue, car la variable API_TAG n'était pas définie par le pipeline CI ce jour-là. Personne ne s'en est rendu compte avant plusieurs heures.
Corrige cette configuration pour qu'un tel incident devienne impossible.