Docker Compose : .env, env_file ou environment ?
Comprenez la différence entre .env, env_file et environment dans Docker Compose, leur ordre de priorité et pourquoi les secrets ne doivent pas rester dans les variables d’environnement.
Les trois éléments que l’on appelle « fichier env »
Docker Compose propose trois mécanismes distincts aux noms très similaires. Le fichier .env remplace les variables ${VARIABLE} dans compose.yaml, après la lecture du YAML par Compose et avant toute validation ou exécution. L’attribut env_file: charge un fichier contenant des paires clé/valeur dans l’environnement du conteneur. L’attribut environment: définit directement des variables dans le conteneur, dans le fichier Compose. Ces mécanismes ne sont pas interchangeables. Lorsque deux d’entre eux définissent la même clé, la valeur retenue dépend d’un ordre de priorité documenté.
Ce guide présente chaque mécanisme en fonctionnement et vérifie la priorité avec une commande que vous pouvez exécuter. Il couvre ensuite le point le plus important : les variables d’environnement sont lisibles par toute personne pouvant exécuter docker inspect. Les mots de passe ne doivent donc pas y être stockés. Si vous débutez avec les fichiers Compose, commencez par les bases de Docker Compose sur un VPS, puis revenez ici pour la configuration.
Le fichier .env sert au fichier Compose, pas au conteneur
Créez un répertoire et placez-y deux fichiers.
mkdir -p ~/envdemo && cd ~/envdemo
printf 'ALPINE_TAG=3.20\n' > .envservices:
demo:
image: alpine:${ALPINE_TAG}
command: printenv ALPINE_TAGDemandez maintenant à Compose ce qu’il a réellement analysé.
docker compose configLa sortie affiche image: alpine:3.20. Le placeholder a disparu, car l’interpolation a eu lieu lorsque Compose a chargé le fichier. Compose recherche .env dans le répertoire du projet : le répertoire indiqué avec --project-directory si vous l’avez défini, sinon le répertoire du premier fichier passé avec -f, et sinon le répertoire courant. Il remplace les placeholders ${NAME} trouvés dans les valeurs YAML. Les clés ne sont jamais interpolées, et un $$ doublé produit un signe dollar littéral au lieu d’une substitution.
Lancez ensuite le service.
docker compose run --rm demoprintenv ALPINE_TAG se termine avec le statut 1 et n’affiche rien. La variable n’existe pas dans le conteneur. C’est le malentendu le plus fréquent : .env a configuré le fichier Compose, pas le processus. Un fichier .env contenant POSTGRES_PASSWORD=hunter2 ne fait absolument rien pour votre base de données, sauf si une partie du fichier Compose y fait référence.
${NAME:-default} fournit une valeur de secours lorsque la variable n’est pas définie ou est vide. ${NAME:?message} force Compose à refuser le démarrage et à afficher votre message. C’est le bon choix pour une valeur qui ne possède aucune valeur par défaut sûre.
env_file charge des variables dans le conteneur
L’attribut env_file: indique un ou plusieurs fichiers dont le contenu devient des variables d’environnement du conteneur.
printf 'GREETING=from_env_file\nAPP_MODE=production\n' > app.envservices:
demo:
image: alpine:3.20
command: printenv GREETING
env_file:
- ./app.envdocker compose run --rm demoCette commande affiche from_env_file. Le format du fichier consiste en lignes simples au format KEY=value, une par ligne, avec # pour commencer un commentaire. Ce n’est pas du shell, même si Compose applique quelques règles similaires à celles du shell. Les guillemets entourant la valeur sont supprimés : KEY="value" et KEY=value produisent donc le même résultat. Les valeurs entre guillemets doubles et les valeurs sans guillemets interpolent les variables ${...}, tandis que les valeurs entre guillemets simples sont utilisées littéralement. Le préfixe export est accepté, mais il n’est pas nécessaire. Les espaces autour du signe = sont supprimés. Le point à surveiller est le commentaire en fin de ligne : dans une valeur sans guillemets, une espace suivie de # commence un commentaire. Utilisez donc des guillemets pour toute valeur qui contient #.
L’absence du chemin env_file provoque une erreur et Compose s’arrête. Rendez-le facultatif si le fichier peut légitimement être absent :
env_file:
- path: ./app.env
required: falseenvironment définit des variables en ligne
services:
demo:
image: alpine:3.20
command: printenv GREETING
environment:
GREETING: from_environmentDeux syntaxes sont acceptées : la forme de mappage ci-dessus et une forme liste utilisant - GREETING=from_environment. Elles se comportent de manière identique. Les deux offrent une possibilité supplémentaire : une clé sans valeur transmet la variable depuis le shell dans lequel vous avez exécuté docker compose. Dans la forme liste, il s’agit d’un - GREETING seul. Dans la forme de mappage, il s’agit de GREETING: sans rien après les deux-points.
environment:
- GREETINGGREETING=from_my_shell docker compose run --rm demoCette commande affiche from_my_shell. Si vous l’exécutez sans définir GREETING dans le shell, Compose ne définit rien et n’affiche aucun avertissement. Il est important de connaître ces échecs silencieux de transmission : un service qui démarre avec une variable de mot de passe vide démarre souvent correctement et reste simplement totalement ouvert.
Quel paramètre prend le dessus
Docker documente l’ordre de priorité, du plus élevé au plus faible : docker compose run -e sur la ligne de commande, puis environment ou env_file, dont la valeur est interpolée depuis votre shell ou depuis un fichier d’environnement, puis environment simple dans le fichier Compose, puis env_file, puis la directive ENV intégrée à l’image.
En pratique : environment: prend le dessus sur env_file:, et -e sur la ligne de commande prend le dessus sur les deux. Vérifiez-le dans un seul fichier.
services:
demo:
image: alpine:3.20
command: printenv GREETING
env_file:
- ./app.env
environment:
GREETING: from_environmentdocker compose run --rm demo
docker compose run --rm -e GREETING=from_cli demo printenv GREETINGLa première commande affiche from_environment : environment: a donc remplacé la valeur de app.env. La seconde affiche from_cli. Rien dans le fichier Compose ne remplace la valeur définie sur la ligne de commande.
Lorsqu’un conteneur se comporte comme si votre configuration n’avait pas été appliquée, ne faites pas de suppositions. docker compose config affiche le fichier entièrement résolu, et docker compose config --environment affiche les variables d’interpolation utilisées par Compose. Dans la plupart des signalements indiquant que « mon fichier d’environnement est ignoré », une même valeur a été définie à deux niveaux différents.
Pourquoi les variables d’environnement fuient
Définissez un mot de passe dans environment: : il est alors stocké sur disque dans la configuration du conteneur et visible par tout utilisateur membre du groupe docker.
docker compose run -d --name leaky -e DB_PASSWORD=hunter2 demo sleep 300
docker inspect leaky --format '{{json .Config.Env}}'La sortie contient "DB_PASSWORD=hunter2" en clair. Trois autres chemins exposent la même valeur. docker compose config l’affiche dans le terminal, ce qui explique comment elle peut être copiée dans un forum d’assistance. Par défaut, les processus enfants héritent de la variable, sauf si le processus parent les démarre explicitement avec un environnement de remplacement. Un processus qui s’exécute avec le même utilisateur que le PID 1, ou qui dispose de CAP_SYS_PTRACE, peut la lire dans /proc/1/environ. Dans un conteneur où tous les processus s’exécutent en tant que root, cela concerne tous les processus. Une application exécutée par un utilisateur sans privilèges alors que le PID 1 s’exécute en tant que root reçoit à la place une erreur de permission sur ce fichier, même si la variable reste présente dans son propre environnement. Les gestionnaires de crash des applications enregistrent aussi régulièrement l’environnement complet dans un journal ou un rapport d’erreur.
L’appartenance au groupe docker équivaut effectivement aux privilèges root sur l’hôte. Il ne s’agit donc pas d’une frontière de privilèges sur laquelle vous pouvez compter. Le guide sur les comptes utilisateur appliquant le principe du moindre privilège sur un VPS explique pourquoi il est préférable de limiter ce groupe sur tout serveur partagé.
Les secrets Compose conservent la valeur dans un fichier
Compose prend en charge les secrets stockés dans des fichiers. La valeur est montée dans le conteneur sous forme de fichier, au lieu d’être injectée dans l’environnement.
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./db_password.txtLe secret est monté à l’emplacement /run/secrets/db_password dans le conteneur. Le nom qui suit la barre oblique est le nom du secret défini dans le bloc secrets: de niveau supérieur.
Le suffixe _FILE est une convention utilisée par les Docker Official Images, notamment postgres, mysql et mariadb. Leurs scripts d’entrypoint recherchent VARNAME_FILE, lisent le fichier et utilisent son contenu. Il ne s’agit pas d’une fonctionnalité Docker. Cette convention ne fonctionne donc que si l’image l’implémente. Consultez la documentation de l’image avant de supposer que SOMETHING_FILE sera pris en charge. Les applications qui ne prennent pas cette convention en charge peuvent souvent lire elles-mêmes le fichier au démarrage. Vous pouvez aussi transmettre le chemin et laisser votre propre entrypoint s’en charger.
Vérifiez le résultat depuis le conteneur en cours d’exécution :
docker compose exec db cat /run/secrets/db_password
docker compose exec db printenv POSTGRES_PASSWORDLa première commande affiche le mot de passe. La seconde n’affiche rien, car la valeur n’a jamais été ajoutée à l’environnement. C’est précisément l’objectif : docker inspect dans ce conteneur affiche uniquement le chemin sans caractère sensible.
Protégez le fichier source sur l’hôte, car le secret n’est pas plus privé que le fichier qui le contient :
chmod 600 db_password.txtLe compromis pragmatique sur un VPS
De nombreuses images self-hosted ne prennent pas en charge les variables _FILE. Les variables d’environnement sont donc le seul moyen de les transmettre. Sur un VPS administré par une seule personne, l’objectif réaliste consiste à éviter que ces valeurs restent dans un fichier lisible par tous dans le répertoire de votre projet, et à les exclure de git.
sudo install -o root -g root -m 600 /dev/null /etc/myapp/app.env
sudo nano /etc/myapp/app.env env_file:
- /etc/myapp/app.envinstall -m 600 crée le fichier avec les permissions déjà définies. Il n’existe donc aucune période pendant laquelle il est lisible par tous. root en est propriétaire. Un utilisateur qui n’a pas les privilèges root sur la machine ne peut donc pas le lire. En revanche, toute personne pouvant exécuter docker peut toujours lire la valeur depuis le conteneur. Ajoutez *.env et .env à .gitignore, puis validez un fichier app.env.example contenant les noms des clés avec des valeurs vides. Un mot de passe validé dans git doit être changé.
La rotation d’une valeur nécessite de redémarrer le service. Les variables d’environnement sont lues une seule fois, au démarrage du processus du conteneur. Modifier le fichier ne change donc rien tant que vous n’exécutez pas docker compose up -d --force-recreate db. C’est le même principe que dans le guide n8n derrière HTTPS sur un VPS, où la clé de chiffrement se trouve en dehors du fichier Compose.
Fractionner la configuration par environnement
Compose lit .env dans le répertoire du projet par défaut. Indiquez un autre emplacement avec --env-file.
docker compose --env-file .env.staging configLes fichiers sont lus dans l’ordre et les fichiers suivants remplacent les valeurs définies précédemment. Conservez les valeurs par défaut non secrètes dans un fichier versionné, et les secrets dans un fichier qui ne quitte jamais le serveur. La même règle s’applique à env_file: : en cas de clé dupliquée, la valeur du dernier fichier indiqué est retenue.
FAQ
Pourquoi mon fichier .env est-il ignoré dans le conteneur ?
Il n’est pas ignoré. Le fichier .env remplace les espaces réservés ${NAME} dans le fichier Compose. Il peut également définir les variables de Compose lui-même, comme COMPOSE_PROJECT_NAME ou COMPOSE_FILE. Il ne définit jamais de variables dans un conteneur. Pour transmettre la valeur au conteneur, référencez-la avec environment: { KEY: "${NAME}" } ou utilisez plutôt env_file: ./that-file.env.
Est-ce que environment prend le dessus sur env_file, ou est-ce l’inverse ?
environment: est prioritaire. L’ordre documenté par Docker place l’attribut environment au-dessus de l’attribut env_file. Les deux sont moins prioritaires que docker compose run -e sur la ligne de commande. Si une clé est définie aux deux endroits, la valeur de env_file est silencieusement ignorée.
Comment voir la valeur finale utilisée par Compose ?
Exécutez docker compose config pour afficher le fichier Compose entièrement résolu, après l’application de toutes les interpolations. Pour un conteneur déjà en cours d’exécution, docker inspect <container> --format '{{json .Config.Env}}' affiche exactement ce que son processus a reçu.
Les secrets Compose sont-ils chiffrés ?
Non. Un secret basé sur un fichier est monté dans le conteneur sous forme de fichier en clair à l’emplacement /run/secrets/<name>. Le fichier source reste également non chiffré sur le disque de l’hôte. L’intérêt concerne la portée, pas le chiffrement : la valeur reste en dehors de l’environnement du conteneur, de la sortie de docker inspect et des dumps mémoire qui affichent l’environnement.
Puis-je utiliser des guillemets et des espaces dans un fichier env ?
Oui. Compose supprime les guillemets externes. Ainsi, KEY="value with spaces" et KEY=value with spaces produisent tous deux value with spaces. Utilisez des guillemets simples lorsque la valeur contient un $ qui ne doit pas être interpolé. Mettez entre guillemets toute valeur contenant #, car ce caractère commence un commentaire lorsqu’il n’est pas entre guillemets. Les espaces autour de = sont supprimés. Ainsi, KEY = value définit toujours KEY.