Docker Compose : les variables d’environnement vous trahissent — les erreurs .env qui cassent la prod

Cet article vous a aidé ?

La panne commence petit. Un conteneur démarre avec NODE_ENV=development, ou votre base de données accepte tout à coup
des connexions avec un mot de passe par défaut. Rien n’a « changé » dans l’application. Le job CI est vert. Vous avez déployé le même
fichier Compose que la semaine dernière.

Ce qui a changé, c’est la partie la plus fragile de votre déploiement : la chaîne invisible de variables d’environnement qui traverse
Docker Compose, votre shell, et un minuscule fichier .env que personne ne relit parce que « ce n’est pas du code ».
C’est du code. Il n’est juste pas linté.

Un modèle mental qui ne vous mentira pas

Docker Compose utilise les variables d’environnement de deux manières différentes, et la plupart des incidents en production surviennent quand les équipes
les traitent comme une seule et même chose :

1) Variables utilisées par Compose lui‑même (au moment du rendu)

Ces variables existent pour rendre la configuration Compose : des éléments comme ${IMAGE_TAG} dans
compose.yaml, COMPOSE_PROJECT_NAME, ou COMPOSE_PROFILES.
Compose les résout avant de démarrer les conteneurs. Si Compose se trompe, vos conteneurs ne seront peut‑être même pas ceux que vous pensez avoir déployés.

2) Variables transmises aux conteneurs (au runtime)

Ces variables font partie de l’environnement du conteneur : ce que votre app lit via getenv.
Elles proviennent de environment:, env_file:, et parfois de votre shell via des passages implicites.

Les variables de rendu influencent le YAML final. Les variables d’exécution influencent le comportement du processus dans le conteneur.
Confondre les deux mène à réparer un bug de conteneur en éditant un profil shell sur l’hôte, puis découvrir que systemd ne lit pas ce profil shell.

Une vérité opérationnelle : vous ne pouvez pas « simplement vérifier le .env ». Il faut vérifier ce que Compose a effectivement rendu et ce que le conteneur a réellement reçu.

Citation à garder sur votre bureau : idée paraphrasée« L’espoir n’est pas une stratégie. » (idée paraphrasée attribuée à
Gordon Sullivan, souvent citée en ingénierie et fiabilité)

Blague n°1 : Les variables d’environnement sont comme les commérages au bureau — tout le monde jure qu’il ne les a pas lancés, mais elles se retrouvent pourtant dans toutes les pièces.

Faits et historique à connaître (pour arrêter de vous disputer avec YAML)

  • Fait 1 : Le fichier .env utilisé par Docker Compose n’est pas automatiquement au même format qu’un script shell. C’est un parseur plus simple « KEY=VALUE » avec ses propres bizarreries.
  • Fait 2 : Compose provient à l’origine de Fig (2014), et beaucoup de son comportement sur les variables est une commodité héritée plutôt qu’une élégance de conception pure.
  • Fait 3 : Compose v2 est implémenté comme un plugin de l’interface Docker CLI, et le comportement peut varier subtilement entre versions parce que le moteur est désormais plus proche de l’écosystème Docker CLI.
  • Fait 4 : Compose utilise les variables d’environnement à la fois pour le rendu de la configuration et pour l’environnement des conteneurs ; des règles de priorité différentes s’appliquent à chaque chemin.
  • Fait 5 : L’interpolation des variables arrive avant la plupart des validations. Une variable manquante peut devenir silencieusement une chaîne vide et former malgré tout une valeur YAML « valide ».
  • Fait 6 : env_file est une entrée d’exécution pour les conteneurs ; il n’influence généralement pas l’interpolation de Compose à moins que vous ne chargiez explicitement les variables dans le shell ou n’utilisiez une chaîne d’outils le faisant.
  • Fait 7 : La commande docker compose config est l’outil le plus proche d’un sérum de vérité : elle montre la configuration entièrement rendue que Compose va exécuter.
  • Fait 8 : Le même projet sur deux hôtes peut se rendre différemment parce que Compose lit l’environnement de l’hôte, le répertoire courant, et les entrées optionnelles --env-file.
  • Fait 9 : COMPOSE_PROJECT_NAME affecte les noms de réseau, de volume et de conteneur. Un changement de project-name peut « orpheliner » d’anciens volumes et en créer de nouveaux.

Carnet de diagnostic rapide

Quand la production brûle, vous n’avez pas besoin de philosophie. Vous avez besoin d’une séquence qui rétrécit rapidement le rayon d’action.
Voici l’ordre que j’utilise parce qu’il sépare les bugs « au moment du rendu » des bugs « au runtime » en quelques minutes.

Première étape : confirmer ce que Compose a rendu

  1. Exécutez docker compose config et inspectez les valeurs interpolées (tags d’images, ports, chemins de volumes,
    nom de projet, profils). Si la config rendue est erronée, ne perdez pas de temps à l’intérieur des conteneurs.
  2. Cherchez des chaînes vides, des valeurs « null » implicites, des valeurs par défaut inattendues, ou des définitions de services dupliquées à cause des profils.

Deuxième étape : confirmer ce que le conteneur a réellement reçu

  1. Inspectez l’environnement du conteneur (docker inspect) ou affichez-le depuis l’intérieur du conteneur
    (env).
  2. Comparez avec ce que vous pensez avoir défini via env_file et environment.

Troisième étape : confirmer quel .env et quel environnement hôte ont été utilisés

  1. Vérifiez le répertoire de travail et le fichier env sélectionné. Si vous avez lancé Compose depuis le mauvais répertoire, vous pourriez
    utiliser le mauvais .env.
  2. Vérifiez CI/CD : passe-t-il --env-file ? Exporte-t-il des variables ? systemd n’écrase-t-il pas l’environnement ?

Si le stockage ou le réseau semble étrange, suspectez le nom de projet et les noms de volumes

Un COMPOSE_PROJECT_NAME changé ou un changement de nom de répertoire peut créer de nouveaux réseaux et de nouveaux volumes.
L’application a « perdu » ses données parce qu’elle écrit sur un volume différent avec un nom différent.

Tâches pratiques : commandes, sorties et décisions

Ce sont les tests sur le terrain. Chacun inclut : une commande, ce que signifie une sortie typique, et la décision à prendre.
Exécutez-les dans l’ordre quand vous ne savez pas où se cache la vérité.

Tâche 1 : Vérifier la version de Compose (le comportement varie)

cr0x@server:~$ docker compose version
Docker Compose version v2.24.6

Sens : Vous êtes sur Compose v2.x. Bien — la plupart des comportements et flags modernes s’appliquent.
Si c’était v1, plusieurs flags et comportements limites diffèrent.
Décision : Notez cette version dans les notes d’incident ; si le comportement diffère entre hôtes, alignez les versions.

Tâche 2 : Voir quel nom de projet Compose pense utiliser

cr0x@server:~$ docker compose ls
NAME                STATUS              CONFIG FILES
payments-prod        running(6)          /srv/payments/compose.yaml

Sens : Le projet est payments-prod. Les réseaux/volumes seront préfixés par ce nom.
Décision : Si vous attendiez un autre nom de projet, arrêtez‑vous : vous pourriez opérer sur le mauvais projet.

Tâche 3 : Rendre la configuration entièrement interpolée (la « vérité »)

cr0x@server:~$ cd /srv/payments
cr0x@server:~$ docker compose config
services:
  api:
    environment:
      DB_HOST: db
      LOG_LEVEL: info
    image: registry.local/payments-api:1.9.3
    ports:
      - mode: ingress
        target: 8080
        published: "8080"
        protocol: tcp
  db:
    environment:
      POSTGRES_DB: payments
    image: postgres:15
volumes:
  payments-prod_db-data: {}
networks:
  default:
    name: payments-prod_default

Sens : L’interpolation a eu lieu. C’est ce que Compose exécutera.
Décision : Si le tag d’image ou le port est erroné ici, le bug se situe dans la résolution des variables (pas dans le runtime du conteneur).

Tâche 4 : Identifier quel fichier env est utilisé

cr0x@server:~$ ls -la /srv/payments/.env
-rw------- 1 root root 412 Jan  2 09:11 /srv/payments/.env

Sens : Un .env local existe dans le répertoire du projet.
Décision : Vérifiez que vous lancez Compose depuis ce répertoire ; sinon vous ne lisez pas ce fichier.

Tâche 5 : Repérer les mines d’espaces et de guillemets dans .env

cr0x@server:~$ sed -n '1,120p' /srv/payments/.env
IMAGE_TAG=1.9.3
DB_PASSWORD=correct-horse-battery-staple
LOG_LEVEL=info
API_BASE_URL=https://payments.internal
BAD_SPACES =oops
QUOTED="literal quotes?"

Sens : BAD_SPACES =oops est suspect : beaucoup de parseurs traitent cette clé comme BAD_SPACES (avec un espace final) ou la rejettent.
QUOTED="literal quotes?" peut préserver les guillemets selon le parseur.
Décision : Corrigez le format : pas d’espaces autour de =, évitez les guillemets sauf si vous connaissez le comportement du parseur.

Tâche 6 : Vérifier si une variable manque au moment du rendu

cr0x@server:~$ grep -n 'IMAGE_TAG' -n /srv/payments/compose.yaml
12:    image: registry.local/payments-api:${IMAGE_TAG}

Sens : Compose a besoin de IMAGE_TAG pour rendre la chaîne d’image.
Décision : Assurez-vous que IMAGE_TAG est défini dans le bon .env ou exporté dans l’environnement utilisé par Compose.

Tâche 7 : Détecter l’interpolation vide silencieuse

cr0x@server:~$ IMAGE_TAG= docker compose config | grep -n 'image:'
7:    image: registry.local/payments-api:

Sens : Un IMAGE_TAG vide rend une référence d’image un peu invalide qui peut quand même passer le parsing YAML.
Décision : Ajoutez des gardes pour variables requises en utilisant les défauts d’interpolation de Compose (voir plus loin) et échouez en CI sur les champs vides.

Tâche 8 : Inspecter l’environnement à l’intérieur d’un conteneur en cours d’exécution

cr0x@server:~$ docker compose exec -T api env | egrep 'DB_|LOG_LEVEL|API_BASE_URL'
API_BASE_URL=https://payments.internal
DB_HOST=db
LOG_LEVEL=info

Sens : Le conteneur a reçu des variables. Si quelque chose manque, c’est un problème d’injection d’environnement au runtime.
Décision : Comparez avec docker compose config et le contenu du env_file.

Tâche 9 : Confirmer ce que Docker pense être l’environnement (autorité)

cr0x@server:~$ docker inspect payments-prod-api-1 --format '{{json .Config.Env}}'
["API_BASE_URL=https://payments.internal","DB_HOST=db","LOG_LEVEL=info","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]

Sens : C’est ce que Docker passera au processus. Si ce n’est pas là, votre application ne le verra pas.
Décision : Si la config Compose le montre mais que l’inspect ne le montre pas, vous avez une dérive de déploiement ou un problème de recréation.

Tâche 10 : Détecter les problèmes de « conteneur non recréé »

cr0x@server:~$ docker compose up -d
[+] Running 2/2
 ✔ Container payments-prod-db-1   Running
 ✔ Container payments-prod-api-1  Running

Sens : Compose n’a pas recréé les conteneurs. Les changements d’env ne s’appliquent pas à un conteneur en cours d’exécution sauf s’il est recréé.
Décision : Si vous avez changé des variables d’env, forcez la recréation : docker compose up -d --force-recreate.

Tâche 11 : Forcer la recréation et confirmer que le nouvel env est appliqué

cr0x@server:~$ docker compose up -d --force-recreate
[+] Running 2/2
 ✔ Container payments-prod-db-1   Running
 ✔ Container payments-prod-api-1  Started

Sens : Le conteneur API a été redémarré/recréé.
Décision : Relancez les Tâches 8/9 pour confirmer que les changements d’environnement ont bien été appliqués.

Tâche 12 : Détecter la dérive de nom de projet qui crée des volumes « nouveaux »

cr0x@server:~$ docker volume ls | grep -E 'payments.*db-data'
local     payments-prod_db-data
local     payments_db-data

Sens : Deux volumes aux noms similaires existent, probablement issus de noms de projet différents.
Décision : Confirmez quel volume est attaché au conteneur DB en cours d’exécution avant de « nettoyer » quoi que ce soit.

Tâche 13 : Confirmer quel volume un conteneur utilise réellement

cr0x@server:~$ docker inspect payments-prod-db-1 --format '{{range .Mounts}}{{println .Name .Destination}}{{end}}'
payments-prod_db-data /var/lib/postgresql/data

Sens : La DB utilise payments-prod_db-data.
Décision : Si l’application « a perdu des données », comparez avec le volume que vous attendiez. Ne supprimez pas de volumes tant que vous n’avez pas prouvé qu’ils sont inutilisés.

Tâche 14 : Identifier quel .env est utilisé quand vous lancez depuis un autre répertoire

cr0x@server:~$ cd /tmp
cr0x@server:~$ docker compose -f /srv/payments/compose.yaml config | head
services:
  api:
    image: registry.local/payments-api:

Sens : L’exécution depuis /tmp a probablement fait manquer à Compose le /srv/payments/.env prévu, donc l’interpolation a échoué.
Décision : Lancez toujours depuis le répertoire du projet ou fournissez --env-file /srv/payments/.env.

Tâche 15 : Prouver la priorité entre l’hôte et .env

cr0x@server:~$ cd /srv/payments
cr0x@server:~$ export IMAGE_TAG=2.0.0
cr0x@server:~$ docker compose config | grep -n 'image:'
7:    image: registry.local/payments-api:2.0.0

Sens : La variable exportée sur l’hôte a écrasé la valeur dans .env.
Décision : En production, évitez de vous reposer sur « ce qui est exporté dans le shell ». R rendez la source d’env explicite.

Tâche 16 : Détecter des CRLF accidentels Windows dans .env (oui, toujours)

cr0x@server:~$ file /srv/payments/.env
/srv/payments/.env: ASCII text, with CRLF line terminators

Sens : Les CRLF peuvent se glisser dans les clés/valeurs, provoquant des « variable non trouvée » déroutants ou des valeurs avec un \r caché.
Décision : Convertissez en LF : sed -i 's/\r$//' /srv/payments/.env, puis rendez de nouveau la config.

Tâche 17 : Confirmer des retours chariot cachés dans une valeur spécifique

cr0x@server:~$ python3 -c 'import os;print(repr(open("/srv/payments/.env","rb").read().splitlines()[-1]))'
b'QUOTED="literal quotes?"\r'

Sens : Ce \r final est réel. Il peut casser des tokens d’auth, des URL ou des mots de passe.
Décision : Normalisez les fins de ligne en CI, et traitez .env comme un artefact texte qui nécessite des contrôles.

Tâche 18 : Montrer les différences entre env_file et environment dans la config finale

cr0x@server:~$ docker compose config | sed -n '1,80p'
services:
  api:
    environment:
      DB_HOST: db
      LOG_LEVEL: info
    image: registry.local/payments-api:1.9.3

Sens : Vous voyez clairement les valeurs inline de environment:. Si vous avez utilisé env_file, il peut ne pas s’expanser inline dans la sortie comme vous l’attendez.
Décision : Si votre audit dépend de voir les variables, ne vous fiez pas seulement à env_file ; utilisez des étapes explicites de validation de configuration.

Priorité et portée : qui gagne quand les variables se percutent

La plupart des équipes ne peuvent pas répondre à cette question sans deviner : « Si je définis FOO dans le shell, dans .env,
et dans environment:, lequel gagne ? » La réponse dépend de si vous parlez du moment du rendu ou du runtime.
C’est pourquoi cela casse en production : les gens parlent à côté les uns des autres.

Priorité au rendu (interpolation dans compose.yaml)

Quand Compose interpole ${VAR} dans le YAML, il regarde ses sources d’environnement. En pratique, l’environnement exporté du processus Compose est le compétiteur majeur. Le .env local est souvent un repli pratique.

En d’autres termes : si votre CI exporte IMAGE_TAG, il écrasera typiquement le .env. Si votre unité systemd exécute Compose avec un environnement essentiellement vide, elle ignorera ce que votre shell interactif avait.

Règle opérationnelle : les variables de rendu doivent être explicites. Passez‑les via la CI de façon contrôlée ou fournissez un fichier d’env explicite via --env-file. Ne laissez pas des shells aléatoires décider.

Priorité au runtime (ce que reçoit le conteneur)

L’environnement du conteneur est construit à partir des définitions de services Compose :

  • environment: les entrées sont explicites et visibles dans le fichier Compose.
  • env_file: charge des paires clé/valeur depuis un fichier dans l’environnement du conteneur.
  • Certaines variables peuvent être transmises depuis l’hôte si vous les référencez dans environment: sans valeur, selon la syntaxe.

Règle pratique : traitez environment: comme une API pour ce que le conteneur attend et
traitez env_file comme un détail d’implémentation pour la manière dont vous lui fournissez les valeurs. En debug, vérifiez toujours ce qui est effectivement arrivé via docker inspect.

Le nom de projet fait partie de l’histoire d’environnement en coulisse

COMPOSE_PROJECT_NAME ressemble à « juste un nom ». Ce n’est pas le cas.
Il change les noms de réseaux et de volumes. Si vous liez vos données à des volumes et votre monitoring aux noms de conteneurs,
le nom de projet est une variable de production, que vous l’admettiez ou pas.

Interpolation et parsing : les bords tranchants de .env et Compose

Le format .env ressemble à du shell. Ce n’est pas du shell. C’est un fichier clé/valeur avec juste assez de flexibilité
pour vous rendre trop confiant.

Espaces blancs : le tueur silencieux

Beaucoup de parseurs traitent KEY =value comme une clé différente de KEY=value, ou ils le rejettent.
Dans les deux cas, vous vous retrouvez avec « KEY non défini » et Compose substitue silencieusement une chaîne vide.

Ne soyez pas « tolérant ». Soyez strict. Pour les fichiers env de production :
pas d’espaces autour du signe égal, et les clés devraient correspondre à [A-Z0-9_]+.

Guillemets : parfois littéraux, parfois retirés, toujours déroutants

Dans certains écosystèmes, FOO="bar" signifie que la valeur est bar sans guillemets. Dans d’autres, ces
guillemets font partie de la valeur. Le comportement de Compose peut vous surprendre selon la voie de parsing empruntée.
La seule position sûre : évitez les guillemets dans .env sauf si vous avez vérifié le comportement avec docker compose config et un conteneur en cours d’exécution.

Défauts d’interpolation : utilisez-les, mais comprenez‑les

Compose prend en charge des motifs comme :
${VAR:-default} et ${VAR?error} dans de nombreux contextes.
C’est là que les équipes peuvent transformer une défaillance silencieuse en une défaillance bruyante.

Si IMAGE_TAG doit exister en prod, rendez‑le obligatoire. Si LOG_LEVEL peut avoir une valeur par défaut, donnez‑lui une valeur par défaut.
Échouez rapidement sur tout ce qui change le comportement d’une manière que vous ne pouvez pas observer.

Vide est différent d’absence

L’interpolation de Compose considère souvent une variable vide comme « définie », ce qui peut contrer les valeurs par défaut. Si une pipeline définit
IMAGE_TAG en chaîne vide (oui, ça arrive), votre ${IMAGE_TAG:-latest} peut ou non se comporter comme vous l’attendez.
Testez cela explicitement dans votre environnement.

.env vs env_file : même look, sémantique différente

Le fichier .env (pour Compose) est utilisé pour l’interpolation des variables de Compose et certaines options Compose.
Le directive env_file: alimente l’environnement des conteneurs au runtime.
Les gens les confondent parce que le contenu des fichiers se ressemble. Le résultat est invariablement chaotique.

Si vous voulez que des valeurs influencent l’interpolation, elles doivent être dans l’environnement que Compose utilise pour le rendu
(export shell, --env-file explicite, ou la gestion d’environnement de votre orchestration). Si vous voulez des valeurs à l’intérieur du conteneur,
elles doivent être dans environment: ou env_file:.

Blague n°2 : Un fichier .env ressemble beaucoup à un tout‑petit — le silence ne signifie pas que tout va bien, cela signifie qu’il faut vérifier immédiatement.

Trois mini‑histoires d’entreprise issues du terrain

Histoire 1 : L’incident causé par une mauvaise hypothèse

Une équipe fintech faisait tourner une API orientée client sur une paire de VM avec Docker Compose. Ils avaient un .env dans le repo
et un prod.env séparé stocké sur l’hôte. Dans leur tête, « Compose charge l’env depuis env_file ». Ils avaient
en partie raison et étaient entièrement condamnés.

Le fichier Compose utilisait ${IMAGE_TAG} pour ancrer l’image de l’API. Les variables runtime du conteneur venaient de
env_file: ./prod.env. Un candidat de release nécessitait un hotfix, donc un ingénieur a mis à jour IMAGE_TAG
dans prod.env, a lancé docker compose up -d, et s’attendait à ce que la nouvelle image se déploie.

Ce ne fut pas le cas. L’interpolation de Compose ne regardait pas env_file pour rendre le champ image:.
Les conteneurs sont restés sur l’ancien tag. Parallèlement, l’ingénieur a aussi mis à jour une variable runtime dans prod.env
en supposant que le conteneur l’avait prise ; ce ne fut pas le cas, parce que Compose n’a pas recréé le conteneur. Ils se sont retrouvés
avec de l’ancien code, de vieilles variables, et une nouvelle croyance.

Deux heures plus tard, l’API renvoyait des erreurs ressemblant à une régression du hotfix. Ce n’en était pas une. Le hotfix n’avait jamais été déployé. Leur monitoring affichait « déploiement réussi » parce que le job s’était terminé ; il n’avait pas validé la sortie de docker compose config ni vérifié les IDs d’images des conteneurs en cours.

La correction fut ennuyeuse : rendre les tags d’images variables de rendu obligatoires et les définir explicitement dans la commande de déploiement,
vérifier avec docker compose config, puis forcer la recréation ou faire rouler correctement les conteneurs. Ils ont aussi cessé
d’utiliser prod.env comme un fichier magique qui « contrôle tout ». Il ne contrôle que ce que vous y branchez.

Histoire 2 : L’optimisation qui s’est retournée contre eux

Une société média voulait accélérer les déploiements. Quelqu’un a remarqué que recréer les conteneurs prend du temps, surtout pour un
service avec beaucoup de sidecars. Ils ont changé le processus : mettre à jour .env, puis lancer docker compose up -d
sans forcer la recréation, pour « éviter les temps d’arrêt ».

Pendant un temps, cela semblait fonctionner — parce que la plupart des changements étaient des tags d’image, et Compose tirerait et redémarrerait
les services quand il détectait une nouvelle image. Mais les variables d’environnement ne sont pas des images. Un changement de configuration critique
a basculé un flag de fonctionnalité pour le routage des requêtes. La moitié de la flotte s’est mise à jour (les nœuds où les conteneurs ont été recréés),
l’autre moitié non. Le résultat fut un comportement « split‑brain » où les requêtes prenaient des chemins différents selon la VM qui les recevait.

Le débogage fut pénible parce que le fichier Compose semblait correct, le .env semblait correct, et les conteneurs étaient tous « up ». Le bug venait du process : ils avaient optimisé en ôtant l’étape qui applique de manière fiable les changements d’env.
Ils avaient introduit de la non‑déterminisme dans le déploiement de configuration.

Le playbook de récupération fut brutal : si l’env a changé, les conteneurs sont recréés. Si vous voulez zéro downtime,
faites‑le avec des load balancers et des redémarrages roulants, pas en espérant que Compose déduise votre intention.

Histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une équipe B2B SaaS faisait tourner des stacks basés sur Compose pour des services internes : métriques, runners de jobs, et une base legacy.
Ils étaient allergiques au « clever ». Leur déploiement en production exigeait trois vérifications :
rendre la config, valider les IDs d’images en cours, et enregistrer la somme de contrôle effective de l’environnement.

Un vendredi, un changement merge a introduit une nouvelle variable RATE_LIMIT_MODE utilisée dans l’interpolation de Compose
pour sélectionner une image sidecar. Le développeur l’a ajoutée à .env.example mais a oublié la source d’env de production.
Le pipeline CI ne l’exportait pas non plus.

Le job de déploiement a échoué tôt parce que leur fichier Compose utilisait ${RATE_LIMIT_MODE?must be set}.
C’est tout le truc : ils ont transformé une interpolation vide silencieuse en un arrêt brutal. Pas de déploiement partiel, pas de comportement mystérieux.

Ils ont corrigé le pipeline, déployé lundi, et personne n’a été pagé. C’était si peu événementiel que l’équipe en a été agacée.
C’est ainsi qu’on sait que c’était correct.

Erreurs courantes : symptôme → cause racine → correctif

1) Symptom : le tag d’image devient vide ou « latest » de façon inattendue

Cause racine : Variable de rendu manquante ou vide, Compose interpole en chaîne vide ; ou la CI exporte une variable vide qui écrase .env.

Correctif : Utilisez l’interpolation requise : image: myapp:${IMAGE_TAG?set IMAGE_TAG}. En CI, échouez si IMAGE_TAG est vide. Validez avec docker compose config.

2) Symptom : « J’ai mis à jour .env mais le conteneur n’a pas changé de comportement »

Cause racine : Le conteneur n’a pas été recréé ; le conteneur en cours conserve l’ancien environnement.

Correctif : Appliquez les changements avec docker compose up -d --force-recreate (ou docker compose restart si approprié, mais la recréation est plus sûre pour les changements d’env). Vérifiez avec docker inspect ... Config.Env.

3) Symptom : la prod utilise des réglages dev même si prod.env existe

Cause racine : Compose lit .env depuis le répertoire de travail courant, pas depuis le chemin prévu ; ou --env-file non fourni dans l’automatisation.

Correctif : Dans systemd/CI, exécutez depuis le répertoire de projet ou spécifiez --env-file /srv/app/.env. Ajoutez une garde qui imprime la somme de contrôle du fichier env pendant le déploiement.

4) Symptom : l’authentification par mot de passe échoue, alors que la valeur « semble correcte »

Cause racine : CRLF ou espaces finaux dans .env injectent des caractères cachés (souvent \r) dans la valeur.

Correctif : Normalisez les fins de ligne (sed -i 's/\r$//'), et validez en affichant la repr ou un hexdump de la valeur dans un conteneur de test contrôlé.

5) Symptom : la base de données a « perdu » des données après un redeploy

Cause racine : Changement de nom de projet (changement de nom de répertoire, COMPOSE_PROJECT_NAME modifié), créant un nouveau volume avec un nom différent.

Correctif : Pincez le nom de projet explicitement pour la production. Auditez docker volume ls et docker inspect des mounts avant nettoyage. Traitez les noms de volumes comme de l’état.

6) Symptom : les variables dans le conteneur ne correspondent pas à ce qui est dans .env

Cause racine : Confusion entre .env (rendu de Compose) et env_file (runtime du conteneur) ; ou l’environnement hôte écrase des valeurs.

Correctif : Décidez quelle source est faisant autorité. Pour des valeurs runtime critiques, utilisez des clés environment: explicites et alimentez‑les depuis un fichier env contrôlé. Pour le rendu, passez via --env-file et validez la sortie de configuration.

7) Symptom : une variable avec des guillemets a un comportement étrange

Cause racine : Les guillemets sont traités littéralement ou retirés différemment de ce que vous attendiez ; plusieurs parseurs dans la chaîne d’outils.

Correctif : Retirez les guillemets dans .env sauf si nécessaire. Quand c’est nécessaire, validez avec docker compose config et inspectez depuis l’intérieur du conteneur.

8) Symptom : le service ne démarre pas, le mapping de ports est absurde

Cause racine : L’interpolation a produit une chaîne de port invalide (vide, non numérique, contient des espaces), mais le YAML passe quand même.

Correctif : Rendre les variables obligatoires et valider les ports en CI en grepant la config rendue. N’utilisez des défauts que pour des valeurs sûres en dev.

9) Symptom : « marche en local, échoue en CI » avec le même fichier Compose

Cause racine : Le shell local exporte des variables et la CI ne le fait pas ; ou la CI a un autre encodage/newlines ; ou la CI exécute depuis un autre répertoire.

Correctif : Rendre la source d’env explicite en CI. Imprimez docker compose config (ou au moins les lignes pertinentes) et assurez-vous que c’est déterministe.

10) Symptom : des secrets apparaissent dans les logs ou les bundles de support

Cause racine : Stocker des secrets dans .env et imprimer la config rendue ou l’environnement du conteneur pendant le debug ; les variables d’env fuient facilement via les listings de processus et les dumps.

Correctif : Utilisez les secrets Compose quand c’est possible, ou des credentials montés en fichier avec des permissions strictes. Dans les outils d’incident, masquez par défaut les sorties d’environnement.

Listes de contrôle / plan étape par étape pour la production

Checklist A : Rendre l’interpolation Compose déterministe

  1. Fixez la source d’env : Dans l’automatisation, lancez toujours avec un chemin --env-file explicite et un répertoire de travail fixé.
  2. Rendez les variables critiques obligatoires : Utilisez ${VAR?message} pour les tags d’images, endpoints externes et noms de projet.
  3. Arrêtez d’exporter des variables au hasard : Nettoyez l’environnement dans les jobs CI. Si c’est nécessaire, définissez‑les explicitement.
  4. Rendez et diff : Conservez la sortie de docker compose config comme artefact de build et diff‑ez‑la par rapport aux déploiements précédents.

Checklist B : Rendre l’environnement runtime du conteneur auditable

  1. Documentez le contrat : Listez les variables d’environnement runtime requises par service (noms, signification, valeurs autorisées).
  2. Privilégiez les clés environment: explicites : Cela rend le contrat visible en revue de code.
  3. Utilisez env_file pour des valeurs en bloc, pas pour un comportement mystérieux : Gardez‑le minimal et structuré. Évitez de mélanger « dev » et « prod » dans le même fichier.
  4. Recréez sur changement d’env : Si l’environnement runtime a changé, les conteneurs doivent être recréés. Planifiez le downtime/les redémarrages roulants en conséquence.

Checklist C : Ne laissez pas l’état dériver (volumes/réseaux)

  1. Pincez le nom de projet : Définissez name: dans le modèle Compose ou COMPOSE_PROJECT_NAME dans une source d’env contrôlée.
  2. Déclarez explicitement les volumes : Utilisez des volumes nommés pour les services état‑ful ; évitez les volumes anonymes accidentels.
  3. Auditez avant nettoyage : Inspectez toujours les mounts et références de conteneurs avant de supprimer des volumes.

Checklist D : Traitez .env comme du code de production

  1. Permissions : chmod 600 .env si il contient du matériel sensible.
  2. Normalisez les fins de ligne : Imposer LF en CI.
  3. Règles de lint : Pas d’espaces autour de =, pas de tabulations, pas d’espaces finaux, schéma de clés prévisible.
  4. Contrôle des changements : Exigez une revue pour les modifications d’env, et conservez un historique (même si le fichier est stocké de manière sécurisée hors Git).

Conseils opérationnels qui préviennent la plupart des incidents .env

Utilisez des valeurs par défaut seulement pour l’ergonomie des développeurs, pas pour la sécurité en production

Les valeurs par défaut comme ${LOG_LEVEL:-debug} conviennent pour le travail local. En production, elles peuvent transformer une configuration manquante en comportement surprenant. Préférez des valeurs explicites dans les sources d’env de production et des variables requises pour tout ce qui touche l’intégrité des données, l’auth ou le routage.

Échouez tôt sur l’hôte, pas tard dans le conteneur

Si une variable est requise, faites échouer au moment du rendu. Vous voulez que le déploiement s’arrête avant de tirer des images, avant de toucher aux volumes, avant de redémarrer quoi que ce soit. C’est moins coûteux et plus sûr.

Cessez de traiter les secrets comme « juste des variables d’environnement »

Les variables d’environnement fuient. Elles se retrouvent dans les rapports de plantage, endpoints de debug, listings de processus, bundles de support accidentels, et captures d’écran humaines. Elles restent aussi dans les métadonnées des conteneurs plus longtemps que vous ne le pensez.
Utilisez les mécanismes de secrets quand vous le pouvez. Si vous ne pouvez pas, séparez au moins secrets et non-secrets et concevez vos commandes de diagnostic pour masquer par défaut.

Rendez la configuration observable

Votre système doit rapporter la version de configuration effective sans divulguer les secrets. Une somme de contrôle de config,
un SHA git, un digest d’image, et une variable « mode » non sensible suffisent généralement pour confirmer que le système est bien celui que vous croyez.

FAQ

1) Compose charge-t-il automatiquement .env ?

Typiquement, oui — .env dans le répertoire du projet est utilisé comme source pratique pour l’interpolation des variables Compose et certaines options Compose. Mais le « répertoire du projet » dépend d’où vous exécutez la commande et comment vous référencez le fichier Compose. Si vous lancez depuis le mauvais répertoire, vous pouvez charger silencieusement le mauvais .env ou aucun.

2) .env est-il identique à env_file ?

Non. .env influence couramment l’interpolation au moment du rendu de Compose. env_file injecte
des variables dans le conteneur au runtime. Les fichiers se ressemblent ; les sémantiques sont différentes. Les confondre est un mode d’échec classique.

3) Pourquoi mon .env n’a-t-il pas pris effet après docker compose up -d ?

Parce que les conteneurs n’absorbent pas magiquement de nouvelles variables d’environnement. Si Compose ne recrée pas le conteneur,
l’environnement en cours reste le même. Utilisez docker compose up -d --force-recreate quand l’env a changé,
et vérifiez via docker inspect.

4) Qui gagne : les variables exportées shell ou .env ?

Dans beaucoup de configurations communes, les variables exportées dans l’environnement qui exécute Compose écrasent les valeurs du .env.
C’est pourquoi « ça marche sur ma machine » arrive : votre shell exporte quelque chose que la CI n’a pas, ou inversement. Rendez la source d’env explicite en automatisation.

5) Puis-je avoir plusieurs fichiers env ?

Oui, mais soyez intentionnel sur leur but : un pour le rendu (passé avec --env-file) et éventuellement un ou plusieurs pour l’injection runtime (env_file: par service). Évitez d’empiler tellement de fichiers que personne ne peut prédire le résultat.

6) Pourquoi mon application voit‑elle des guillemets dans les valeurs ?

Parce que votre parseur peut traiter les guillemets littéralement. Le format .env n’est pas une norme universelle et
différents outils interprètent guillemets et échappements différemment. Si vous avez besoin de caractères spéciaux, testez le chemin exact :
rendu via docker compose config et runtime via docker inspect.

7) Comment empêcher que des variables vides glissent en production ?

Utilisez l’interpolation requise (${VAR?message}) pour les valeurs critiques et ajoutez des contrôles CI qui échouent si
la config rendue contient des tags d’image vides, des ports vides ou des hostnames vides. C’est l’un des correctifs les plus efficaces à déployer.

8) Pourquoi un redéploiement a‑t‑il créé de nouveaux volumes et « effacé » les données ?

Probablement un changement de nom de projet. Compose préfixe les noms de volume et de réseau avec le nom de projet, qui vient du
nom du répertoire, d’une configuration explicite ou de l’environnement. Pincez‑le pour la prod afin que les volumes restent stables. Ensuite, confirmez que le conteneur DB est attaché au volume attendu avant tout nettoyage.

9) Est‑il sûr d’imprimer docker compose config dans les logs CI ?

Pas toujours. Si vous inlinez des secrets dans le fichier Compose ou les interpolez dans des champs affichés, vous pouvez fuir des identifiants. Si vous devez imprimer la config, redactez les clés sensibles ou n’imprimez que des lignes ciblées (références d’images, ports, réglages non sensibles).

10) Quand dois‑je utiliser les secrets Compose plutôt que des vars d’environnement ?

Utilisez les secrets quand vous le pouvez : identifiants, tokens API, clés privées, tout ce que vous regretteriez de voir dans un log ou un dump.
Les variables d’environnement conviennent pour la configuration non sensible et les toggles de fonctionnalités. Si vous devez utiliser des vars pour des secrets, verrouillez les permissions et réduisez les endroits où elles sont affichées.

Prochaines étapes à faire cette semaine

  1. Ajoutez une « vérification de rendu » en CI : exécutez docker compose config et échouez sur les champs critiques vides
    (tags d’image, ports, hostnames). Sauvegardez la config rendue comme artefact en masquant les secrets.
  2. Rendez les variables critiques obligatoires : convertissez ${VAR} en ${VAR?set VAR} pour
    les points d’interpolation critiques en production.
  3. Pincez le nom de projet en production : évitez la dérive accidentelle des volumes et réseaux. Traitez‑le comme de l’état.
  4. Standardisez l’exécution des déploiements : répertoire de travail fixe, --env-file explicite,
    et une politique : les changements d’env exigent recréation ou redémarrage roulant.
  5. Cessez de stocker les secrets dans des .env occasionnels : migrez vers un mécanisme de secrets ou des montages de fichiers et
    adaptez les outils de diagnostic pour éviter de les divulguer pendant les incidents.

Docker Compose fonctionne. Ce qui ne va pas, ce sont les hypothèses non dites autour de .env.
Rendez les variables explicites, la config rendue observable, et l’environnement des conteneurs vérifiable.
Alors le prochain « mystère de régression » deviendra un diff de cinq minutes plutôt qu’un weekend.

← Précédent
Google Search Console « Page avec redirection » : quand c’est acceptable et quand ça pose problème
Suivant →
ZFS ZVOL vs Dataset : la décision qui façonne vos douleurs futures

Laisser un commentaire