infra / docker
Variables d'environnement et secrets
Explication
Ce que vous allez apprendre
- Injecter des variables d'environnement à un conteneur au lancement, sans reconstruire l'image
- Distinguer
ARG(build-time uniquement) d'ENV(build-time ET run-time) - Comprendre pourquoi un secret ne doit jamais être placé dans une instruction
ENV/ARGclassique - Utiliser
--env-filepour injecter des secrets proprement depuis un fichier non versionné - Connaître les alternatives plus robustes (Docker secrets, BuildKit secrets, gestionnaire externe)
Dans quel contexte ?
Une entreprise découvre, lors d'un audit de sécurité, qu'un ancien Dockerfile contenait ENV DATABASE_PASSWORD=motdepasse123. Même après avoir modifié le Dockerfile et reconstruit l'image, l'ancien mot de passe reste visible dans l'historique des couches de toutes les images déjà publiées sur le registre, consultable avec docker history. Le mot de passe doit être considéré comme définitivement compromis et changé partout — une leçon coûteuse sur pourquoi un secret ne doit jamais transiter par une variable ENV.
Rendre une image configurable
Une même image construite une seule fois doit souvent se comporter différemment selon le contexte : mode debug activé en développement, adresse de base de données différente entre un environnement de test et la production, etc. Plutôt que de reconstruire une image différente pour chaque cas, on lui injecte des variables d'environnement au moment du lancement — l'image reste identique, seul son comportement au démarrage change.
| Mécanisme | Durée de vie | Visible dans l'image finale |
|---|---|---|
ARG | Build uniquement | Oui, dans l'historique des couches |
ENV | Build ET run-time | Oui, dans l'historique et l'inspect |
--env-file / -e au run | Run uniquement | Non |
| Docker secret / BuildKit secret | Selon le mécanisme, jamais gravé en couche | Non |
Piège dangereux
Une valeur définie avec ENV (ou même ARG) dans un Dockerfile reste inscrite dans les métadonnées de l'image, consultables avec docker history ou docker inspect, même longtemps après la construction. Un mot de passe placé ainsi n'est jamais réellement caché — n'importe qui ayant accès à l'image peut le retrouver.
ARG et ENV, une distinction à ne pas rater
Ce sont deux mécanismes proches mais avec une différence essentielle de durée de vie. ARG n'existe que pendant la construction de l'image et disparaît ensuite — il ne sera jamais visible dans le conteneur en cours d'exécution. ENV, à l'inverse, est fixé dans l'image et reste accessible aussi bien au moment du build qu'à chaque exécution du conteneur.
Pourquoi un secret n'est jamais une simple variable ENV
C'est le point le plus important de cette leçon, et une erreur de sécurité très fréquente chez les débutants : une valeur définie avec ENV (ou même ARG) dans un Dockerfile reste inscrite dans les métadonnées de l'image, consultables avec docker history ou docker inspect, même longtemps après la construction, et même si le Dockerfile a depuis été modifié. Un mot de passe de base de données placé ainsi n'est donc jamais réellement caché — n'importe qui ayant accès à l'image peut le retrouver.
Les bonnes pratiques, du plus simple au plus robuste
La solution la plus simple consiste à injecter les secrets uniquement au lancement (--env-file), jamais au moment du build, avec un fichier exclu du contrôle de version. Une solution plus robuste consiste à monter le secret comme un fichier plutôt qu'une variable (Docker secrets, ou les montages secrets de BuildKit), car un fichier temporaire n'est jamais gravé dans une couche de l'image. En production sérieuse, on délègue en général cette responsabilité à un gestionnaire de secrets externe dédié (Vault, AWS Secrets Manager...), qui injecte les valeurs au tout dernier moment, au démarrage.
Commandes & code
Variables d'environnement et secrets
# Passer des variables d'environnement à un conteneur
docker run -d -e DEBUG=false -e PORT=8000 mon-api:1.0
docker run -d -e DATABASE_URL="postgresql://user:pass@db:5432/app" mon-api:1.0
# Depuis un fichier .env (évite de tout taper en ligne de commande)
cat .env
# DEBUG=false
# PORT=8000
# DATABASE_URL=postgresql://user:pass@db:5432/app
docker run -d --env-file .env mon-api:1.0
# Vérifier les variables réellement injectées dans un conteneur
docker exec mon-api env
docker inspect --format '{{.Config.Env}}' mon-api# ARG (build-time uniquement) vs ENV (build-time ET run-time)
ARG BUILD_VERSION=dev
ENV APP_VERSION=$BUILD_VERSION
# ARG disparaît après le build, ENV reste accessible dans le conteneur en cours d'exécutiondocker build --build-arg BUILD_VERSION=1.4.2 -t mon-api:1.4.2 .# --- ATTENTION : ne JAMAIS mettre un secret dans une variable ENV du Dockerfile ---
# Une variable ENV reste visible dans "docker history" et "docker inspect", même après le build.
# Un ARG passé au build reste aussi visible dans l'historique des couches par défaut.# --- Bonnes pratiques pour les secrets ---
# 1. Injecter au run, jamais au build, via un fichier .env non versionné (.gitignore)
echo ".env" >> .gitignore
docker run -d --env-file .env.production mon-api:1.0
# 2. Docker secrets (nécessite Swarm actif), stocké chiffré, monté en fichier (pas en variable)
echo "supersecret" | docker secret create db_password -
docker service create --name api --secret db_password mon-api:1.0
# Dans le conteneur : le secret est lisible en tant que FICHIER dans /run/secrets/db_password
# 3. BuildKit secrets : injecte un secret au build SANS le laisser dans les couches de l'image
# syntax=docker/dockerfile:1
# RUN --mount=type=secret,id=npm_token \
# NPM_TOKEN=$(cat /run/secrets/npm_token) npm install
docker build --secret id=npm_token,src=./npm_token.txt -t mon-api:1.0 .
# 4. En production réelle : préférer un gestionnaire de secrets externe
# (Vault, AWS Secrets Manager, SOPS) qui injecte au démarrage plutôt qu'un simple .envRésumé
-e/--env-fileinjectent des variables au runtime ;ARGn'existe qu'au moment du build.- Ne jamais placer un secret dans une instruction
ENV/ARGclassique : il reste visible dans l'historique de l'image. docker secret(Swarm) et les secrets BuildKit (--mount=type=secret) évitent qu'un secret persiste dans une couche.
Exercices pratiques
Mission : purger un mot de passe compromis d'une image Docker
Objectif : Corriger une fuite de secret via ENV et mettre en place une injection propre au runtime.
Contexte
Un audit de sécurité découvre ENV DATABASE_PASSWORD=motdepasse123 dans un ancien Dockerfile. Le Dockerfile a été corrigé depuis, mais les anciennes images restent publiées sur le registre.