Retour au cours

infra / docker-compose

Secrets avec Docker Compose

Leçon 111 exercice

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 *_FILE pour 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écanismeVisible dans docker inspect ?Où l'application le lit
environment: classiqueOui, en clairprocess.env.DB_PASSWORD
secrets: ComposeNonFichier /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.

yaml
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
javascript
// 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");
yaml
# 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
bash
# 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_password

Résumé

  • Les secrets sont montés en fichiers dans /run/secrets/, pas en variables d'env visibles.
  • Toujours utiliser le pattern *_FILE côté application pour lire le contenu du fichier.
  • Les fichiers sources de secrets ne doivent JAMAIS être committés (.gitignore).
  • external: true référence un secret déjà provisionné hors de ce fichier Compose.

Exercices pratiques

1 disponible
1

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.

Résoudre l’exercice →