Retour au cours

infra / docker-compose

Variables d'environnement et fichiers .env

Leçon 51 exercice

Explication

Ce que vous allez apprendre

  • Distinguer le fichier .env racine (interpolation du YAML) de env_file (variables du conteneur)
  • Savoir laquelle de environment ou env_file l'emporte en cas de valeur en double
  • Fournir une valeur par défaut avec la syntaxe ${VAR:-defaut}
  • Charger un fichier .env différent selon l'environnement avec --env-file
  • Ne jamais committer de secrets dans un fichier .env versionné

Dans quel contexte ?

Un développeur clone le dépôt boutique-api et lance docker compose up, en pensant que la variable DB_PASSWORD définie dans son .env racine sera automatiquement visible dans le code Node.js du conteneur. Elle ne l'est pas : l'application plante avec une erreur "password authentication failed" car cette variable n'a servi qu'à interpoler le YAML, pas à alimenter le conteneur. Cette leçon explique cette distinction essentielle.

Le principe de départ : ne pas coder en dur

D'abord, une règle de base issue des applications "12-factor" : ne jamais coder en dur une valeur qui change selon l'environnement, comme un mot de passe de base de données ou une URL d'API. Ces valeurs doivent vivre en dehors du code, dans des variables d'environnement.

Premier mécanisme : le fichier .env pour l'interpolation

Compose propose un fichier .env à la racine du projet. Son rôle est précis : il remplit les ${VARIABLE} présentes DANS le fichier YAML lui-même, avant même que Compose ne parle à Docker. C'est de la simple substitution de texte dans ta configuration.

Un piège à anticiper tout de suite

Attention, ce .env racine ne finit PAS automatiquement dans le conteneur. Beaucoup de débutants pensent que placer une variable dans ce fichier suffit à la rendre disponible pour l'application — ce n'est pas le cas.

Deuxième mécanisme : env_file et environment pour le conteneur

Pour qu'une variable soit réellement disponible dans le conteneur, via process.env ou équivalent, il faut utiliser env_file ou environment. Ces deux clés injectent véritablement des variables à l'intérieur du conteneur, contrairement au .env racine qui ne fait que de l'interpolation.

Que se passe-t-il en cas de conflit ?

Une fois qu'on utilise les deux mécanismes ensemble, une question se pose naturellement : que se passe-t-il si une variable est définie à la fois par env_file et environment ? La réponse est simple : c'est toujours environment qui gagne, car c'est la déclaration la plus explicite.

MécanismeOù il agitVisible dans le conteneur ?
.env racineInterpolation du fichier YAML (${VAR})Non, sauf s'il est repris dans environment
env_file:Injection de tout un fichier dans le conteneurOui
environment:Injection directe, clé par cléOui, prioritaire sur env_file

Prérequis

Il est utile d'avoir déjà manipulé des variables d'environnement dans un contexte de développement classique (Node.js, Python...) avant cette leçon, même sans Docker.

Une astuce pratique : les valeurs par défaut

Pour aller plus loin, la syntaxe ${VAR:-valeur_par_défaut} permet de fournir une valeur de secours si la variable n'est pas définie, tout en gardant la possibilité de la surcharger plus tard.

Le piège majeur à ne jamais commettre

Ne committe jamais un .env contenant de vrais secrets dans Git. Il doit systématiquement figurer dans le .gitignore, avec à la place un .env.example versionné qui documente simplement les variables attendues, sans leurs valeurs sensibles.

Piège fréquent

Un .env committé par erreur dans Git reste visible dans l'historique même après suppression du fichier : un git rm .env ne suffit pas à effacer un secret déjà poussé. Ajoute .env au .gitignore dès la création du projet, avant même le premier commit.

La prochaine leçon s'appuie directement sur ces notions pour résoudre un autre problème classique : s'assurer qu'un service ne démarre pas avant qu'un autre soit vraiment prêt.

Commandes & code

Variables d'environnement & .env

yaml
services:
  api:
    image: myorg/api:latest
    environment:
      # Valeur en dur
      NODE_ENV: production
      # Interpolée depuis .env ou l'environnement shell, avec valeur par défaut
      PORT: "${API_PORT:-3000}"
      DATABASE_URL: "postgres://user:${DB_PASSWORD}@db:5432/app"
    env_file:
      - .env.api          # charge toutes les variables du fichier dans le conteneur
bash
# .env à la racine du projet : lu automatiquement par "docker compose"
# pour l'INTERPOLATION du fichier YAML (pas injecté au conteneur par défaut)
COMPOSE_PROJECT_NAME=technologik
API_PORT=3000
DB_PASSWORD=s3cr3t_dev_only
bash
# .env.api : chargé dans le conteneur via "env_file"
NODE_ENV=production
LOG_LEVEL=info
FEATURE_FLAG_NEW_UI=true
bash
# Passer une variable ponctuelle sans toucher au .env
API_PORT=4000 docker compose up -d

# Spécifier un fichier .env différent
docker compose --env-file .env.staging up -d

# Vérifier les valeurs réellement interpolées
docker compose config
yaml
# Différence env_file vs environment : "environment" a toujours la priorité
services:
  api:
    env_file: .env.api          # LOG_LEVEL=info dans le fichier
    environment:
      LOG_LEVEL: debug          # écrase la valeur du fichier -> debug gagne

Résumé

  • .env racine = interpolation des ${VAR} dans le YAML lui-même.
  • env_file: = variables injectées dans le conteneur au runtime.
  • environment: a toujours priorité sur env_file: en cas de conflit.
  • Ne jamais committer un .env contenant de vrais secrets (.gitignore).

Exercices pratiques

1 disponible
1

Mission : DB_PASSWORD introuvable côté application

Objectif : Diagnostiquer une variable présente dans le .env racine mais absente du conteneur, et résoudre un conflit de priorité.

Contexte

L'application boutique-api plante avec "password authentication failed" alors que DB_PASSWORD=s3cr3t_dev_only est bien défini dans le .env à la racine du projet. Par ailleurs, .env.api contient LOG_LEVEL=info, chargé via env_file, alors que le service définit aussi environment: LOG_LEVEL: debug.

Corrige la configuration et explique les mécanismes en jeu.

Résoudre l’exercice →