Le vrai risque : vos secrets voyagent dans vos images
En 2024, GitGuardian a détecté plus de 12,8 millions de secrets exposés dans des dépôts publics GitHub — un chiffre en hausse de 28 % par rapport à l'année précédente selon le State of Secrets Sprawl 2025. Les images Docker Hub en sont l'un des vecteurs les plus sous-estimés.
Lorsque vous écrivez ARG API_KEY dans un Dockerfile ou que vous passez -e DB_PASSWORD=hunter2 au démarrage, ce secret ne reste pas confiné au runtime. Il peut se retrouver :
- dans les layers de l'image buildée (inspectable avec docker history --no-trunc) ;
- dans les métadonnées de l'image exportée sur Docker Hub (docker inspect) ;
- dans votre fichier .env commité par erreur lors d'un git add . pressé.
Les scanners automatiques (Trivy, Grype, GitGuardian) parcourent Docker Hub en continu. Un dépôt public avec un secret en clair est indexé en quelques minutes. La fenêtre d'exposition est quasi nulle.
5 erreurs de configuration qui exposent vos secrets
- Variables d'env en clair dans
docker-compose.yml—environment: DB_PASSWORD: hunter2est lisible par quiconque accède au fichier ou à l'image. - Fichier
.envcommité —.gitignoremanqué une fois, et le secret est dans l'historique git pour toujours (même aprèsgit rm, accessible viagit log). ARGpassé aubuildpuis copié dans l'image — lesARGsont gravés dans les métadonnées de layer et lisibles pardocker history.- Secrets dans les logs — une application qui logue ses variables d'environnement au démarrage (Spring Boot, Rails en debug, certains serveurs Node) imprime les credentials dans
docker logs. - Volumes bind-mount sur
/rootou le répertoire du projet — un fichier.envmonté depuis l'hôte reste accessible à tout processus du conteneur avec les droits root.
Prérequis
Pour suivre cet article, vous avez besoin de :
- Un VPS Linux (Debian 12 ou Ubuntu 22.04+) avec accès root.
- Docker Engine ≥ 24 et Docker Compose ≥ 2.24 (vérifier avec docker compose version).
- Pour SOPS : age installé (paquet age sur Debian/Ubuntu, ou binaire depuis github.com/FiloSottile/age).
- Pour .env.vault : Node.js ≥ 18 et le CLI dotenvx (npm install -g @dotenvx/dotenvx).
Aucun cluster Swarm n'est requis pour les méthodes présentées ici.
Méthode 1 — Docker Secrets en Compose non-Swarm (pas à pas)
Vérifier la version de Compose
Docker Secrets fonctionne sans Swarm depuis Docker Compose v2.24.0 (release du 11 janvier 2024). Vérifiez :
docker compose version # Docker Compose version v2.27.1Si vous êtes en dessous de la v2.24, mettez Compose à jour avant de continuer (
apt-get install docker-compose-pluginsur Debian/Ubuntu).Créer les fichiers de secrets
Les secrets Docker Compose en mode non-Swarm sont des fichiers sur l'hôte, montés en tmpfs dans le conteneur. Créez-les hors du répertoire du projet :
mkdir -p /etc/myapp/secrets echo -n 'motdepasse-db-solide' > /etc/myapp/secrets/db_password echo -n 'cle-api-stripe-xxxxx' > /etc/myapp/secrets/stripe_key chmod 600 /etc/myapp/secrets/* chown root:root /etc/myapp/secrets/*Le
-ndeechoévite le saut de ligne final — certaines applications lisent le fichier en entier, saut de ligne compris, ce qui invalide la clé.Déclarer les secrets dans docker-compose.yml
services: app: image: monapp:latest secrets: - db_password - stripe_key environment: # on indique le CHEMIN, pas la valeur DB_PASSWORD_FILE: /run/secrets/db_password STRIPE_KEY_FILE: /run/secrets/stripe_key secrets: db_password: file: /etc/myapp/secrets/db_password stripe_key: file: /etc/myapp/secrets/stripe_keyNotez l'usage de la convention
*_FILE: votre application doit lire la variableDB_PASSWORD_FILE, ouvrir le fichier indiqué et en lire le contenu. Les images officielles PostgreSQL, MySQL, Redis et la plupart des images Bitnami supportent nativement cette convention — vérifiez la documentation de votre image.Vérifier le montage dans le conteneur
Après
docker compose up -d, inspectez le montage :docker compose exec app ls -la /run/secrets/ # -r-------- 1 root root 20 Sep 20 08:12 db_password # -r-------- 1 root root 28 Sep 20 08:12 stripe_key docker inspect monapp_app_1 | grep -A5 Mounts # "Type": "tmpfs", # "Destination": "/run/secrets",Le montage est de type tmpfs : le contenu vit en mémoire vive, jamais écrit sur le disque du conteneur. Il disparaît à l'arrêt du conteneur.
Ce que Docker Secrets ne fait PAS — à comprendre avant de continuer
Docker Secrets n'est pas un coffre-fort hermétique. Ce qu'il ne protège pas :
- Le fichier source (
/etc/myapp/secrets/db_password) reste sur le disque hôte en clair — root sur la VM y accède toujours.
- Tout processus du conteneur (PID 1 comme un sous-processus lancé par l'app) peut lire/run/secrets/*.
- Une variable d'environnement dérivée du secret (DB_PASSWORD=$(cat /run/secrets/db_password)dans un entrypoint) remet le secret dans l'env, visible pardocker inspect.Docker Secrets protège contre la fuite dans les layers d'image et dans
docker-compose.yml. Il ne protège pas contre un processus compromis à l'intérieur du conteneur.
Docker Secrets vs .env.vault vs SOPS — quelle méthode pour quel contexte
Faites défiler le tableau
| Critère | Docker Secrets (Compose) | .env.vault (dotenvx) | SOPS + age |
|---|---|---|---|
| Swarm requis | Non (depuis Compose v2.24) | Non | Non |
| Secret stocké en clair sur l'hôte | Oui (fichier source) | Non (chiffré en dépôt) | Non (chiffré en dépôt) |
| KMS distant nécessaire | Non | Non (clé symétrique locale possible) | Non (age fonctionne off-line) |
| Rotation sans redéploiement | Non (restart nécessaire) | Non (rebuild .env) | Non (rebuild .env) |
| CI/CD : injection dans le pipeline | Complexe (fichiers à provisionner) | Simple (variable `DOTENV_PRIVATE_KEY`) | Moyen (clé age en secret CI) |
| Courbe d'apprentissage | Faible (natif Compose) | Faible (CLI dotenvx) | Moyenne (syntaxe YAML + clés age/GPG) |
Configuration complémentaire : rotation et chiffrement au repos
Rotation des secrets Docker Compose. Docker Compose non-Swarm ne supporte pas la rotation à chaud (contrairement à Swarm qui peut mettre à jour un secret sans couper le service). Pour changer un secret :
# 1. Écrire la nouvelle valeur
echo -n 'nouveau-motdepasse' > /etc/myapp/secrets/db_password
# 2. Redémarrer le service concerné
docker compose restart appChiffrer les fichiers sources avec age. Si vous voulez chiffrer les fichiers sur l'hôte (contre un vol de snapshot ou une sauvegarde compromise), SOPS + age permet de stocker les fichiers chiffrés et de les déchiffrer au démarrage :
# Générer une clé age
age-keygen -o /root/.config/sops/age/keys.txt
# Chiffrer le fichier de secret
sops --encrypt --age $(age-keygen -y /root/.config/sops/age/keys.txt) \
/etc/myapp/secrets/db_password > /etc/myapp/secrets/db_password.enc
# Dans le script de démarrage, déchiffrer avant docker compose up
sops --decrypt /etc/myapp/secrets/db_password.enc > /etc/myapp/secrets/db_passwordAudit des accès. Activez les logs Docker avec journald (--log-driver=journald dans /etc/docker/daemon.json) pour conserver une trace de qui a lancé quels conteneurs et quand.
Ce que Docker Secrets ne protège pas
Docker Secrets monte le secret en tmpfs dans /run/secrets/ : c'est un filet de sécurité contre les fuites dans les images et les fichiers Compose, pas contre un processus compromis à l'intérieur du conteneur. Tout process qui tourne dans le conteneur — y compris un shell obtenu par RCE — peut lire /run/secrets/*. Et root sur l'hôte accède toujours au fichier source.
Si votre modèle de menace inclut un conteneur compromis, la bonne réponse est un secret manager externe (HashiCorp Vault, AWS Secrets Manager, Infisical self-hosted) qui délivre les secrets par API avec authentification, sans jamais les écrire sur le disque du conteneur.
Dépannage — erreurs courantes
unknown shorthand flag: 's' in -s lors de docker compose up
Vous utilisez l'ancienne commande docker-compose (v1, Python). Passez à docker compose (v2, plugin Go) avec apt-get install docker-compose-plugin.
secrets are only supported when deploying to a swarm
Votre version de Docker Compose est inférieure à la v2.24. Vérifiez avec docker compose version et mettez à jour.
Le conteneur démarre mais /run/secrets/db_password est vide
Vérifiez que le fichier source existe et n'est pas vide : cat /etc/myapp/secrets/db_password | wc -c. Un fichier vide crée un montage tmpfs vide, sans erreur.
permission denied en lisant /run/secrets/
Les fichiers sont montés avec les permissions du fichier source. Si votre processus tourne sous un utilisateur non-root dans le conteneur, ajustez les permissions du fichier hôte : chmod 640 /etc/myapp/secrets/db_password et vérifiez le GID du processus.
docker inspect montre encore la variable d'env en clair
Vous avez déclaré le secret mais aussi passé la valeur dans environment:. Retirez l'entrée en valeur directe et n'utilisez que la convention *_FILE dans environment:.
Vos secrets sous contrôle — et la suite
Docker Secrets en mode Compose non-Swarm élimine la principale cause de fuite : les credentials en clair dans les fichiers de configuration et les layers d'images. La méthode tient en cinq étapes, fonctionne sans infrastructure externe et s'intègre dans n'importe quel workflow existant.
Pour aller plus loin :
- Checklist de production : les 10 points incontournables pour un docker-compose.yml prêt pour la prod.
- Premiers pas Docker sur VPS : démarrer avec Docker sur VPS si vous êtes en train de construire votre premier environnement.
- Durcissement de l'OS : checklist de durcissement Linux pour sécuriser la couche hôte sur laquelle Docker tourne.