DevToolBoxGRATUIT
Blog

Docker Compose Secrets & Variables d'environnement

10 min de lecturepar DevToolBox

Les variables d'environnement sont le principal mecanisme de configuration des conteneurs dans Docker Compose. Mais il existe 4 facons differentes de les transmettre, chacune avec des comportements, des implications de securite et des pieges distincts. Ce guide couvre toutes les approches, des simples fichiers .env aux secrets Docker de production.

4 facons de passer des variables d'environnement dans Docker Compose

Docker Compose prend en charge quatre methodes distinctes pour injecter des variables d'environnement dans les conteneurs.

1. En ligne avec la directive environment

Definissez des paires cle-valeur directement dans votre docker-compose.yml. Ideal pour la configuration non sensible et specifique au service.

services:
  api:
    image: node:20-alpine
    environment:
      NODE_ENV: production
      DB_HOST: postgres
      DB_PORT: "5432"
      LOG_LEVEL: info

  # List syntax (also valid)
  worker:
    image: node:20-alpine
    environment:
      - NODE_ENV=production
      - QUEUE_NAME=tasks
      - CONCURRENCY=4

2. Fichier externe avec env_file

Chargez des variables depuis un ou plusieurs fichiers externes. Ideal pour gerer des groupes de variables et garder les secrets hors du controle de version.

services:
  api:
    image: node:20-alpine
    env_file:
      - .env              # Shared variables
      - .env.api          # Service-specific variables
      - .env.local        # Local overrides (gitignored)

  db:
    image: postgres:16
    env_file:
      - .env
      - .env.db

3. Transmission des variables shell

Passez des variables du shell hote au conteneur. Ideal pour les pipelines CI/CD et les valeurs dynamiques.

# In your shell:
export API_KEY=sk-abc123
export DEBUG=true

# docker-compose.yml
services:
  api:
    image: node:20-alpine
    environment:
      - API_KEY          # Passes host $API_KEY into container
      - DEBUG            # Passes host $DEBUG into container
      - CI               # Passes host $CI (empty string if unset)

4. Substitution de variables dans docker-compose.yml

Interpolez les variables d'environnement hote dans les valeurs de votre fichier Compose.

# .env file
POSTGRES_VERSION=16
APP_PORT=3000
REPLICAS=3

# docker-compose.yml
services:
  db:
    image: postgres:${POSTGRES_VERSION}    # Resolved from .env or shell
  app:
    ports:
      - "${APP_PORT}:3000"                 # Resolved from .env or shell
    deploy:
      replicas: ${REPLICAS}               # Resolved from .env or shell

Tableau comparatif

MethodeIdeal pourDans VCS ?Secret-safe ?
environmentNon-sensitive defaultsYesNo
env_fileGrouped config, secretsNo (.gitignore)Partial
Shell passthroughCI/CD, dynamic valuesN/APartial
${} substitutionCompose file templatingYes (file), No (.env)No

Fichier .env : syntaxe, emplacement et interpolation

Docker Compose lit automatiquement un fichier .env dans le meme repertoire que votre docker-compose.yml.

Regles de syntaxe

# Comments start with #
# Blank lines are ignored

# Simple key=value (no spaces around =)
DB_HOST=localhost
DB_PORT=5432

# Values with spaces need quotes
APP_NAME="My Docker App"
GREETING='Hello World'

# Multi-line values (double quotes only)
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
-----END RSA PRIVATE KEY-----"

# Variable expansion (Compose v2.20+)
BASE_URL=https://api.example.com
HEALTH_URL=${BASE_URL}/health

# Export prefix is ignored (compatible with shell source)
export SECRET_KEY=mysecretkey

# Empty values
EMPTY_VAR=
ALSO_EMPTY=''

Emplacement et priorite

Compose cherche .env dans le repertoire du projet. Vous pouvez le remplacer avec --env-file :

# Default: reads .env from project directory
docker compose up

# Override with --env-file
docker compose --env-file .env.staging up

# Multiple --env-file flags (Compose v2.24+)
docker compose --env-file .env --env-file .env.local up

# Project directory structure:
my-project/
  docker-compose.yml    # Compose file
  .env                  # Auto-loaded by Compose
  .env.staging          # Loaded with --env-file
  .env.production       # Loaded with --env-file

Interpolation dans docker-compose.yml

Les variables de .env sont disponibles pour la substitution dans docker-compose.yml, mais elles ne sont PAS automatiquement injectees dans les conteneurs :

# .env
TAG=3.2.1
EXTERNAL_PORT=8080

# docker-compose.yml
services:
  web:
    image: myapp:${TAG}              # Uses TAG from .env -> myapp:3.2.1
    ports:
      - "${EXTERNAL_PORT}:80"        # Uses EXTERNAL_PORT from .env -> 8080:80
    environment:
      - TAG                           # WRONG! This passes host $TAG, not .env TAG
      - TAG=${TAG}                   # RIGHT! Explicitly interpolate .env value

# Key insight:
# .env -> docker-compose.yml interpolation   (automatic)
# .env -> container environment              (NOT automatic, use env_file)

env_file vs environment : differences et pieges

Ces deux directives se ressemblent mais se comportent differemment.

DirectiveComportement
environment:Inlined in docker-compose.yml. Visible in version control. Supports variable substitution. Overrides env_file.
env_file:Loaded from external file(s). Can be gitignored. No variable substitution inside the file. Lower priority than environment.
.env (auto)Only for Compose file interpolation (${} syntax). NOT injected into containers. Lowest priority.

Pieges critiques

  • 1. Priorite : les valeurs environment ecrasent les valeurs env_file pour la meme cle.
  • 2. Le fichier .env (charge automatiquement) sert uniquement a l'interpolation du fichier Compose. Il n'injecte PAS de variables dans les conteneurs.
  • 3. Les chemins env_file sont relatifs au fichier docker-compose.yml, pas au repertoire de travail actuel.
  • 4. Si un env_file est manquant, Compose echouera. Utilisez required: false (Compose v2.24+) pour le rendre optionnel.
# Precedence demonstration:

# .env.shared
DB_HOST=shared-db
DB_PORT=5432

# docker-compose.yml
services:
  api:
    env_file:
      - .env.shared              # DB_HOST=shared-db, DB_PORT=5432
    environment:
      DB_HOST: override-db       # Overrides env_file! DB_HOST=override-db
      # DB_PORT not listed here  # Keeps env_file value: DB_PORT=5432

# Result inside container:
# DB_HOST=override-db    (from environment, overrides env_file)
# DB_PORT=5432           (from env_file)

# Optional env_file (Compose v2.24+):
services:
  api:
    env_file:
      - path: .env.local
        required: false          # Won't fail if file doesn't exist

Docker Secrets : secrets bases sur des fichiers pour la production

Les secrets Docker offrent une alternative plus securisee aux variables d'environnement pour les donnees sensibles.

Etape 1 : Creer vos fichiers secrets

# Create secret files (don't commit these!)
echo "SuperSecretPassword123" > db_password.txt
echo "sk-prod-abc123xyz789" > api_key.txt

# Add to .gitignore
echo "*.txt" >> .gitignore
echo "secrets/" >> .gitignore

# Alternative: use a secrets directory
mkdir secrets
echo "SuperSecretPassword123" > secrets/db_password
echo "sk-prod-abc123xyz789" > secrets/api_key

Etape 2 : Definir les secrets dans docker-compose.yml

# docker-compose.yml
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password  # Note: _FILE suffix
    secrets:
      - db_password                    # Grant access to this secret

  api:
    image: myapp:latest
    secrets:
      - db_password
      - api_key
    # Secrets available at:
    # /run/secrets/db_password
    # /run/secrets/api_key

# Top-level secrets definition
secrets:
  db_password:
    file: ./secrets/db_password        # From local file
  api_key:
    file: ./secrets/api_key            # From local file

  # Alternative: use environment variable as source
  # jwt_secret:
  #   environment: JWT_SECRET_VALUE    # From host env var (Compose v2.23+)

Etape 3 : Lire les secrets dans votre application

# Inside the container, secrets are plain text files:
$ docker compose exec api cat /run/secrets/db_password
SuperSecretPassword123

$ docker compose exec api cat /run/secrets/api_key
sk-prod-abc123xyz789

$ docker compose exec api ls -la /run/secrets/
total 8
-r--r--r-- 1 root root 24 Jan  1 00:00 db_password
-r--r--r-- 1 root root 22 Jan  1 00:00 api_key

Etape 4 : Gerer les secrets dans votre code

// Node.js - Read secret from file
const fs = require('fs');

function getSecret(name) {
  const secretPath = `/run/secrets/${name}`;
  try {
    return fs.readFileSync(secretPath, 'utf8').trim();
  } catch (err) {
    // Fallback to environment variable (for development)
    return process.env[name.toUpperCase()];
  }
}

const dbPassword = getSecret('db_password');
const apiKey = getSecret('api_key');
# Python - Read secret from file
import os

def get_secret(name: str) -> str:
    secret_path = f"/run/secrets/{name}"
    try:
        with open(secret_path) as f:
            return f.read().strip()
    except FileNotFoundError:
        # Fallback to environment variable
        return os.environ.get(name.upper(), "")

db_password = get_secret("db_password")
api_key = get_secret("api_key")

Pourquoi les secrets plutot que les variables d'environnement ?

  • Les variables d'environnement peuvent fuiter via docker inspect, /proc/environ, les logs d'erreur
  • Les secrets sont bases sur des fichiers et accessibles uniquement dans /run/secrets/
  • Les secrets prennent en charge le controle d'acces granulaire par service
  • De nombreuses bases de donnees supportent nativement le suffixe _FILE pour les fichiers secrets

Substitution de variables : syntaxe ${VAR:-default}

Docker Compose supporte la substitution de variables style shell avec des valeurs par defaut.

SyntaxeResultat
${VAR}Value of VAR. Error if unset.
${VAR:-default}Value of VAR if set, otherwise "default".
${VAR-default}Value of VAR if set (even if empty), otherwise "default".
${VAR:?error msg}Value of VAR if set, otherwise exit with "error msg".
${VAR?error msg}Value of VAR if set (even if empty), otherwise exit with "error msg".
${VAR:+replacement}"replacement" if VAR is set and non-empty, otherwise empty.
${VAR+replacement}"replacement" if VAR is set (even if empty), otherwise empty.

Exemple pratique

# docker-compose.yml with variable substitution
services:
  web:
    image: nginx:${NGINX_VERSION:-1.25-alpine}     # Default: 1.25-alpine
    ports:
      - "${WEB_PORT:-80}:80"                        # Default: 80
      - "${SSL_PORT:-443}:443"                      # Default: 443
    volumes:
      - ${CONFIG_PATH:-./nginx.conf}:/etc/nginx/nginx.conf:ro

  api:
    image: ${REGISTRY:-docker.io}/myapp:${TAG:?TAG is required}
    #                                     ^ Fails if TAG is not set
    environment:
      NODE_ENV: ${NODE_ENV:-development}
      LOG_LEVEL: ${LOG_LEVEL:-info}
      DATABASE_URL: postgres://${DB_USER:-postgres}:${DB_PASS:?DB_PASS required}@db:5432/${DB_NAME:-myapp}

  db:
    image: postgres:${PG_VERSION:-16}
    volumes:
      - ${DATA_DIR:-./data}/postgres:/var/lib/postgresql/data

Configuration multi-environnement : dev/staging/production

Les projets reels necessitent des configurations differentes. Voici des patterns eprouves.

Pattern 1 : Fichiers override (recommande)

Utilisez un docker-compose.yml de base avec des fichiers override specifiques a l'environnement.

# File structure:
project/
  docker-compose.yml              # Base configuration
  docker-compose.override.yml     # Dev overrides (auto-loaded!)
  docker-compose.staging.yml      # Staging overrides
  docker-compose.prod.yml         # Production overrides
# docker-compose.yml (base)
services:
  api:
    image: myapp:${TAG:-latest}
    restart: unless-stopped
    depends_on:
      - db
      - redis

  db:
    image: postgres:16
    volumes:
      - pg-data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine

volumes:
  pg-data:
# docker-compose.override.yml (development - auto-loaded)
services:
  api:
    build: .
    volumes:
      - .:/app                          # Hot reload
      - /app/node_modules
    ports:
      - "3000:3000"
      - "9229:9229"                     # Debugger
    environment:
      NODE_ENV: development
      LOG_LEVEL: debug
      DB_HOST: db

  db:
    ports:
      - "5432:5432"                     # Expose DB for local tools
    environment:
      POSTGRES_PASSWORD: devpassword    # OK for dev only
# docker-compose.prod.yml (production)
services:
  api:
    # No build, no volumes, no debug port
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production
      LOG_LEVEL: warn
    env_file:
      - .env.production
    secrets:
      - db_password
      - api_key
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '1.0'
          memory: 512M

  db:
    # No exposed ports
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 1G

secrets:
  db_password:
    file: ./secrets/db_password
  api_key:
    file: ./secrets/api_key
# Usage:

# Development (auto-loads docker-compose.override.yml)
docker compose up

# Staging
docker compose -f docker-compose.yml -f docker-compose.staging.yml up -d

# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# Validate merged config before deploying
docker compose -f docker-compose.yml -f docker-compose.prod.yml config

Pattern 2 : Fichiers .env specifiques

Utilisez --env-file pour charger differents ensembles de variables par environnement.

# .env.development
TAG=latest
APP_PORT=3000
DB_PASSWORD=devpassword
LOG_LEVEL=debug
REPLICAS=1

# .env.staging
TAG=rc-1.2.3
APP_PORT=3000
DB_PASSWORD=staging-secret-pw
LOG_LEVEL=info
REPLICAS=2

# .env.production
TAG=1.2.3
APP_PORT=3000
DB_PASSWORD=prod-ultra-secret-pw
LOG_LEVEL=warn
REPLICAS=3

# Usage:
docker compose --env-file .env.development up       # Dev
docker compose --env-file .env.staging up -d         # Staging
docker compose --env-file .env.production up -d      # Production

Pattern 3 : Profiles pour services optionnels

Utilisez les profiles Compose pour inclure ou exclure des services par environnement.

# docker-compose.yml
services:
  api:
    image: myapp:${TAG:-latest}
    ports:
      - "3000:3000"

  db:
    image: postgres:16
    volumes:
      - pg-data:/var/lib/postgresql/data

  # Dev-only services
  adminer:
    image: adminer
    ports:
      - "8080:8080"
    profiles:
      - dev                           # Only starts with --profile dev

  mailhog:
    image: mailhog/mailhog
    ports:
      - "1025:1025"
      - "8025:8025"
    profiles:
      - dev                           # Only starts with --profile dev

  # Monitoring (staging + production)
  prometheus:
    image: prom/prometheus
    profiles:
      - monitoring                    # Only starts with --profile monitoring

volumes:
  pg-data:

# Usage:
docker compose --profile dev up                     # Includes adminer + mailhog
docker compose --profile monitoring up -d           # Includes prometheus
docker compose --profile dev --profile monitoring up  # Both profiles

Checklist securite : ce qu'il ne faut JAMAIS mettre dans les variables d'environnement

Les variables d'environnement sont pratiques mais inheremment non securisees pour les donnees sensibles.

NE JAMAIS faireFaire plutot
Coder en dur les mots de passe dans docker-compose.ymlUtiliser des fichiers .env (gitignored) ou Docker secrets
Commiter les fichiers .env dans le controle de versionAjouter .env au .gitignore, commiter .env.example
Utiliser les variables d'environnement pour les cles priveesLes monter comme fichiers via volumes ou Docker secrets
Journaliser les variables d'environnement dans le codeMasquer les valeurs sensibles dans les logs
Partager les fichiers .env par Slack ou emailUtiliser un gestionnaire de secrets (Vault, AWS Secrets Manager)
Utiliser les memes secrets en dev/staging/productionGenerer des secrets uniques par environnement
# .gitignore — essential entries for Docker projects
.env
.env.*
!.env.example
secrets/
*.pem
*.key
*.crt
# .env.example — commit this as a template
# Database
DB_HOST=localhost
DB_PORT=5432
DB_USER=myapp
DB_PASSWORD=CHANGE_ME

# API Keys
API_KEY=CHANGE_ME
JWT_SECRET=CHANGE_ME

# Application
NODE_ENV=development
LOG_LEVEL=debug
APP_PORT=3000

Debogage : pourquoi ma variable d'environnement ne se charge pas ?

Suivez ce processus systematique quand une variable d'environnement n'atteint pas votre conteneur.

Etape 1 : La variable est-elle definie ?

Verifiez qu'elle existe dans votre fichier .env, env_file ou directive environment.

Etape 2 : Atteint-elle le conteneur ?

Executez docker compose exec <service> env pour voir toutes les variables.

Etape 3 : Le fichier .env est-il au bon endroit ?

Le fichier .env doit etre dans le meme repertoire que docker-compose.yml.

Etape 4 : Y a-t-il un conflit de noms ?

Verifiez les fautes de frappe, la casse et la priorite.

Etape 5 : Le fichier Compose est-il valide ?

Executez docker compose config pour voir la configuration resolue.

Etape 6 : La variable est-elle surchargee ?

Les variables shell du hote ecrasent les valeurs .env. Verifiez avec echo $VAR_NAME.

Commandes de debogage utiles

# 1. See the fully resolved Compose config (all variables substituted)
docker compose config

# 2. Check environment variables inside a running container
docker compose exec api env

# 3. Check a specific variable
docker compose exec api sh -c 'echo $DATABASE_URL'

# 4. Inspect container config (shows env vars, mounts, etc.)
docker inspect <container_id> --format '{{json .Config.Env}}' | jq .

# 5. Check if .env file is being read
docker compose config --format json | jq '.services.api.environment'

# 6. Verify env_file exists and is readable
docker compose config 2>&1 | grep -i "env_file"

# 7. Run a one-off container to test environment
docker compose run --rm api env | sort

# 8. Check for shell variable conflicts
env | grep -i "DB_"

# 9. Validate Compose file syntax
docker compose config --quiet && echo "Valid" || echo "Invalid"

# 10. Show variable substitution warnings
docker compose --verbose up 2>&1 | grep -i "variable"

Questions frequentes

Quelle est la difference entre .env et env_file dans Docker Compose ?

Le fichier .env est lu automatiquement pour la substitution dans docker-compose.yml. La directive env_file charge les variables directement dans l'environnement du conteneur. Ils servent des objectifs differents.

Comment passer des variables d'environnement du hote au conteneur ?

Listez le nom de la variable sans valeur dans la directive environment ou utilisez la syntaxe ${MY_VAR} dans docker-compose.yml.

Les variables d'environnement Docker sont-elles securisees ?

Non. Elles peuvent etre vues avec docker inspect et apparaissent dans /proc/environ. Utilisez Docker secrets pour les donnees sensibles.

Quel est l'ordre de priorite des variables d'environnement ?

Du plus eleve au plus bas : 1) docker compose run -e, 2) Variables shell hote, 3) Directive environment, 4) Directive env_file, 5) Dockerfile ENV.

Comment utiliser des variables differentes pour dev et production ?

Utilisez des fichiers override : docker-compose.yml de base + docker-compose.override.yml pour dev + docker-compose.prod.yml pour la production.

Quelle difference entre Docker secrets et variables d'environnement ?

Les secrets sont montes comme fichiers dans /run/secrets/ plutot que comme variables d'environnement. Ils sont plus securises et supportent le suffixe _FILE.

𝕏 Twitterin LinkedIn
Cet article vous a-t-il aidé ?

Restez informé

Recevez des astuces dev et les nouveaux outils chaque semaine.

Pas de spam. Désabonnez-vous à tout moment.

Essayez ces outils associés

🐳Docker Compose GeneratorYMLYAML Validator & Formatter.gi.gitignore Generator

Articles connexes

Aide-mémoire Docker Compose : Services, volumes et réseaux

Référence Docker Compose : définitions de services, volumes, réseaux, variables d'environnement et exemples de stacks.

Guide .env : Bonnes pratiques des variables d'environnement

Maîtrisez les fichiers .env et les variables d'environnement.