infra / docker-compose
Secrets avec Docker Compose
Explication
Ce que vous allez apprendre
- Comprendre pourquoi une variable d'environnement classique n'est pas un mécanisme sûr pour un secret
- Monter un secret comme fichier dans
/run/secrets/plutôt que comme variable visible - Adapter le code applicatif au pattern
*_FILEpour lire un secret depuis un fichier - Utiliser un secret externe déjà provisionné par un pipeline CI ou Swarm
- Éviter de committer par erreur les fichiers sources de secrets dans Git
Dans quel contexte ?
Un audit de sécurité sur le projet boutique-api révèle que le mot de passe de la base de données apparaît en clair dans la sortie de docker inspect boutique-api-db-1, visible par quiconque a un accès Docker sur le serveur, même sans droit d'admin sur l'application. Cette leçon montre comment remplacer cette variable d'environnement par un secret monté en fichier, invisible dans docker inspect.
Le problème des variables d'environnement classiques
D'abord, un constat gênant : une variable d'environnement définie sur un conteneur est visible en clair par quiconque a accès à docker inspect. Elle apparaît parfois même dans les logs de processus.
Pourquoi ce n'est pas acceptable pour des données sensibles
Pour un mot de passe de base de données ou une clé privée, cette visibilité n'est pas acceptable en production. Il faut un mécanisme qui garde ces valeurs vraiment confidentielles, même en cas d'inspection du conteneur.
L'idée de Compose : un fichier, pas une variable
Les secrets de Compose répondent à ce besoin autrement. Plutôt que d'injecter la valeur sensible directement, Compose monte le contenu du secret dans un fichier, à l'intérieur du conteneur, sous /run/secrets/<nom>, en lecture seule.
Comment l'application doit lire ce secret
L'application ne reçoit donc jamais le secret en clair via son environnement : elle doit lire le CONTENU d'un fichier au démarrage. C'est le rôle du pattern *_FILE, comme DB_PASSWORD_FILE : on indique où trouver le secret, jamais sa valeur directement.
Pourquoi c'est plus sûr qu'une variable classique
Un fichier monté avec des permissions restreintes, comme mode: 0400, est bien plus difficile à exfiltrer accidentellement qu'une variable visible dans toute inspection du conteneur. C'est le même principe que les gestionnaires de secrets d'entreprise, comme Vault ou AWS Secrets Manager.
| Mécanisme | Visible dans docker inspect ? | Où l'application le lit |
|---|---|---|
environment: classique | Oui, en clair | process.env.DB_PASSWORD |
secrets: Compose | Non | Fichier /run/secrets/db_password |
Bonne pratique
Adopte systématiquement le pattern *_FILE côté application : la variable DB_PASSWORD_FILE indique OÙ lire le mot de passe, jamais sa valeur directement. Cela permet au même code de fonctionner aussi bien avec un secret Compose qu'avec un secret monté par Kubernetes plus tard.
Un cas particulier : le secret externe
Il existe aussi des secrets marqués external: true. Ceux-là doivent déjà exister au niveau Docker avant le démarrage de la stack, par exemple créés via Swarm ou un pipeline CI ; sinon Compose échoue au démarrage.
Les pièges à éviter absolument
Premier piège : committer par erreur les fichiers sources des secrets dans Git — ils doivent systématiquement figurer dans .gitignore. Deuxième piège, plus subtil : continuer côté application à lire process.env.DB_PASSWORD au lieu du fichier monté, ce qui annule tout le bénéfice du mécanisme sans que rien ne le signale.
Maintenant que les données sensibles sont protégées service par service, la leçon suivante prend du recul et regarde comment organiser toute la configuration à travers plusieurs environnements : dev, staging, production.
Commandes & code
Secrets Compose
secrets: évite de faire transiter des données sensibles par des variables d'environnement visibles dans docker inspect ou les logs de process.
services:
api:
build: ./api
secrets:
- db_password
- api_signing_key
environment:
# On indique où LIRE le secret plutôt que sa valeur en clair
DB_PASSWORD_FILE: /run/secrets/db_password
db:
image: postgres:16
secrets:
- source: db_password
target: postgres_password # nom du fichier monté dans /run/secrets/
mode: 0400
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
secrets:
db_password:
file: ./secrets/db_password.txt # fichier local (ajouté au .gitignore !)
api_signing_key:
file: ./secrets/api_signing_key.pem// Côté application : lire le secret depuis le fichier monté, pas depuis process.env
const fs = require("fs");
function readSecret(envFileVar, fallbackEnvVar) {
const filePath = process.env[envFileVar];
if (filePath) return fs.readFileSync(filePath, "utf8").trim();
return process.env[fallbackEnvVar]; // fallback dev sans secret file
}
const dbPassword = readSecret("DB_PASSWORD_FILE", "DB_PASSWORD");# Secret externe : déjà créé au niveau Docker (ex: via Swarm ou CI), pas géré par ce fichier
secrets:
tls_cert:
external: true
name: prod_tls_cert# Vérifier qu'un secret est bien monté en lecture seule dans le conteneur
docker compose exec api ls -la /run/secrets/
docker compose exec api cat /run/secrets/db_passwordRésumé
- Les secrets sont montés en fichiers dans
/run/secrets/, pas en variables d'env visibles. - Toujours utiliser le pattern
*_FILEcôté application pour lire le contenu du fichier. - Les fichiers sources de secrets ne doivent JAMAIS être committés (
.gitignore). external: trueréférence un secret déjà provisionné hors de ce fichier Compose.
Exercices pratiques
Mission : le mot de passe visible dans docker inspect
Objectif : Migrer un secret exposé en variable d'environnement vers un secret Compose monté en fichier.
Contexte
Un audit de sécurité sur boutique-api révèle que POSTGRES_PASSWORD, défini en clair via environment: sur le service db, apparaît intégralement dans la sortie de docker inspect boutique-api-db-1. Le code applicatif, lui, continue de lire directement process.env.DB_PASSWORD.
Corrige la configuration ET le raisonnement de l'équipe sur ce que cette correction change vraiment.