Tutoriel

Docker Secrets en production : protéger ses secrets sur VPS

Sécurité & Monitoring7 min de lecture5 étapes

Un docker-compose.yml avec des clés API en clair, c'est une fuite qui attend son scanner. Cet article explique comment éliminer ce risque sur un VPS root : Docker Secrets natif (sans Swarm depuis Compose v2.24), .env.vault et SOPS — chaque méthode avec ses commandes exactes et ses vraies limites.

Sommaire· Le vrai risque : vos secrets voyagent dans vos images1/9
  1. 01Le vrai risque : vos secrets voyagent dans vos images
  2. 025 erreurs de configuration qui exposent vos secrets
  3. 03Prérequis
  4. 04Méthode 1 — Docker Secrets en Compose non-Swarm (pas à pas)
  5. 05Docker Secrets vs .env.vault vs SOPS — quelle méthode pour quel contexte
  6. 06Configuration complémentaire : rotation et chiffrement au repos
  7. 07Ce que Docker Secrets ne protège pas
  8. 08Dépannage — erreurs courantes
  9. 09Vos secrets sous contrôle — et la suite

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.ymlenvironment: DB_PASSWORD: hunter2 est lisible par quiconque accède au fichier ou à l'image.
  • Fichier .env commité.gitignore manqué une fois, et le secret est dans l'historique git pour toujours (même après git rm, accessible via git log).
  • ARG passé au build puis copié dans l'image — les ARG sont gravés dans les métadonnées de layer et lisibles par docker 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 /root ou le répertoire du projet — un fichier .env monté 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)

  1. 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.1

    Si vous êtes en dessous de la v2.24, mettez Compose à jour avant de continuer (apt-get install docker-compose-plugin sur Debian/Ubuntu).

  2. 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 -n de echo évite le saut de ligne final — certaines applications lisent le fichier en entier, saut de ligne compris, ce qui invalide la clé.

  3. 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_key

    Notez l'usage de la convention *_FILE : votre application doit lire la variable DB_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.

  4. 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.

  5. 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 par docker 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èreDocker Secrets (Compose).env.vault (dotenvx)SOPS + age
Swarm requisNon (depuis Compose v2.24)NonNon
Secret stocké en clair sur l'hôteOui (fichier source)Non (chiffré en dépôt)Non (chiffré en dépôt)
KMS distant nécessaireNonNon (clé symétrique locale possible)Non (age fonctionne off-line)
Rotation sans redéploiementNon (restart nécessaire)Non (rebuild .env)Non (rebuild .env)
CI/CD : injection dans le pipelineComplexe (fichiers à provisionner)Simple (variable `DOTENV_PRIVATE_KEY`)Moyen (clé age en secret CI)
Courbe d'apprentissageFaible (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 app

Chiffrer 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_password

Audit 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.

Contrôlez votre stack, contrôlez vos secrets

Un VPS root avec accès Docker complet : vous décidez de la gestion des secrets, de la surface d'attaque, de chaque couche de votre infrastructure.

Besoin d'aide ?

Parcourez notre centre d'aide et notre FAQ, ou contactez notre équipe — rappel, WhatsApp ou e-mail. Support en français, anglais et arabe.

Écrire sur WhatsApps'ouvre dans un nouvel onglet