Le problème
Reprenons notre boutique en ligne. Les services Symfony
order, stock,
shipping et billing tournent dans
deux environnements, staging puis
production. Chacun a son propre Dokploy, qui
pilote Docker Swarm sur ses serveurs.
La CI construit une image par commit. Dans chaque environnement, un job de promotion transmet le digest à déployer à l'API locale de Dokploy. Nous avons décrit ce modèle dans l'article « build once, promote many » : il laisse une question ouverte, où vivent les secrets ?
Ici, ce sont le mot de passe de la base de
order, les identifiants RabbitMQ, la clé de
paiement et les
jetons Mercure.
Le réflexe courant consiste à les ranger dans la forge : secrets GitHub Actions, variables CI/CD de GitLab, groupes de variables Azure DevOps. Le pipeline les écrit ensuite dans chaque Dokploy, par son API. Ce choix a plusieurs effets.
La forge voit plus de monde que la production. Développeurs, runners, intégrations et actions tierces y accèdent. Selon la documentation de GitHub, toute personne qui peut écrire dans un dépôt a accès en lecture à tous ses secrets.
GitLab et Azure DevOps décrivent le même risque : du code poussé dans la définition du pipeline peut extraire les variables, même masquées. Sur une instance GitLab auto-hébergée, quiconque accède à la base et au fichier de secrets de l'instance peut déchiffrer ces variables.
Le secret sort de la forge par le pipeline. Dans le job, la valeur est accessible à chaque script lancé. GitHub prévient que le masquage des journaux n'est pas garanti, par exemple pour une valeur encapsulée dans du JSON.
La CI détient une clé de chaque Dokploy. Pour écrire une variable, sa clé doit accéder au service : elle lit donc aussi ses autres variables. Le job de promotion vit dans l'environnement pour qu'aucune connexion ne parte de la zone connectée : il faudrait rouvrir ce chemin.
Ranger les secrets dans Dokploy demande aussi des précautions. Avant la version 0.29.12, publiée le 13 juillet 2026, Dokploy gardait les variables en clair dans sa base. Quelle que soit la version, chaque valeur finit en clair dans la spécification du service Swarm.
Les options
Les secrets de la forge, poussés par le pipeline
- Ce qu'elle règle : un seul outil, une interface prête à l'emploi.
- Ce qu'elle coûte : tout ce qui précède. La CI devient un chemin vers chaque environnement, et les lecteurs d'un secret de production sont les contributeurs du dépôt.
Des variables Dokploy, saisies dans chaque environnement
Dokploy range les variables à trois niveaux : projet,
environnement et service. Seules celles du service
atteignent le conteneur ; elles citent les autres par
${{project.NOM}} ou
${{environment.NOM}}. Depuis la version
0.29.12, Dokploy les chiffre dans sa base en AES-256-GCM.
- Ce qu'elle règle : rien à installer, et la valeur ne quitte pas la zone.
- Ce qu'elle coûte : ni historique ni journal des lectures. La clé de chiffrement dérive d'un secret Docker du même Swarm que la base, et quiconque ouvre le service dans Dokploy lit ses valeurs.
Le coffre de Symfony, avec la seule clé de déchiffrement dans Dokploy
secrets:set chiffre chaque valeur avec la clé
publique du coffre, et les fichiers chiffrés sont
versionnés. Dokploy ne détient que
SYMFONY_DECRYPTION_SECRET, la clé privée
encodée en base64. Avec une image unique, les coffres de
staging et de production voyagent
dans l'image, et APP_RUNTIME_ENV choisit le
bon.
- Ce qu'elle règle : des secrets versionnés, relus en pull request, et une seule valeur à protéger par environnement.
- Ce qu'elle coûte : la forge garde les valeurs chiffrées pour toujours, et une clé divulguée ouvre tout l'historique. Changer un secret impose un commit, un build et une promotion : la rotation suit le rythme des livraisons.
Un coffre dans la zone, référencé par Dokploy
Depuis la version 0.30.0, publiée le 14 août 2026, Dokploy
sait lire un gestionnaire de secrets externe. Une variable
${{vault.<fournisseur>.<référence>}}
est résolue au déploiement, par exemple depuis OpenBao ou
Vault (moteur KV v2). La valeur n'est pas stockée dans la
base de Dokploy.
- Ce qu'elle règle : une source de vérité par environnement, hors de la base, des sauvegardes et de l'API de Dokploy. Le coffre journalise chaque lecture du jeton de Dokploy.
- Ce qu'elle coûte : un service critique de plus, qui demande sauvegardes testées et procédure de secours, comme le rappelle l'OWASP. Dokploy s'y authentifie par un jeton statique, gardé en clair dans sa base et jamais renouvelé.
Les secrets Docker (/run/secrets) ne sont pas
proposés pour une application Dokploy, seulement dans un
fichier Compose déployé en stack.
Notre recommandation
Les secrets de production vivent dans le Dokploy de l'environnement qui les consomme. La forge ne porte que des paramètres non secrets, et la CI ne détient ni secret de production ni clé de Dokploy.
Par défaut, nous recommandons de les saisir comme variables de service dans chaque Dokploy, et de durcir ce qui les entoure : comptes, clé, sauvegardes. Un coffre OpenBao placé dans la zone prend le relais au signal décrit plus bas. Si le projet utilise le coffre de Symfony, nous le réservons au développement : les variables d'environnement l'emportent sur ses valeurs.
Pourquoi pas un coffre d'emblée ? La valeur résolue finit dans le service Swarm, comme une variable Dokploy.
Selon la documentation de Dokploy, quiconque modifie les variables et déploie dans un projet assigné lit tout ce que le jeton atteint. Cela inclut les secrets des autres services. Le coffre ajoute un journal des lectures de Dokploy, pas moins de lecteurs.
La forge garde le code, les fichiers de promotion et les noms des variables attendues.
Chaque environnement a son propre jeu de secrets :
-
son Dokploy et ses comptes : les développeurs entrent dans
celui de
staging, pas dans celui deproduction; -
des valeurs différentes : la clé de paiement de
stagingest une clé de test du prestataire ; -
aucune valeur de production copiée en
staging, même pour déboguer.
Les compromis assumés
Un accès au service vaut un accès à ses secrets.
Quiconque accède à billing dans Dokploy lit ses
variables : onglet Environment, API
application.one, terminal du conteneur. Un rôle
personnalisé (licence Enterprise) n'y change rien : en
version 0.30.8, l'API et le terminal ne vérifient que
l'accès au service.
Les nœuds Swarm font partie du périmètre.
docker service inspect sur un manager, comme
docker inspect sur le nœud de la tâche, affiche
chaque variable en clair. Le groupe
docker donne des droits équivalents à root,
tout comme la clé du job de promotion, qui peut monter un
chemin de l'hôte.
Aucun journal des lectures. Dokploy ne trace pas qui lit une variable. Ses journaux d'audit, réservés à la licence Enterprise, disent qui a modifié les variables d'un service, et quand.
Deux chemins de livraison. Le code passe par la CI et le job de promotion, les secrets par une saisie dans la zone. Une nouvelle variable secrète demande une étape hors du pipeline, que le job de promotion vérifie.
Le signal qui doit faire réexaminer ce choix : un audit qui exige un journal des lectures de chaque secret. Des secrets lus hors de Dokploy appellent aussi un coffre dans la zone. À l'inverse, un coffre que personne n'exploite vraiment protège moins que des variables Dokploy bien tenues.
Mise en œuvre
Les exemples utilisent Dokploy 0.30.8, la dernière version
au 3 octobre 2026, et Symfony 7.4. L'application Dokploy de
billing s'appelle shop-billing,
comme son service Swarm.
1. Inventorier les secrets
| Secret | Services | Rotation |
|---|---|---|
DATABASE_PASSWORD |
order |
planifiée, puis rechargement du service |
MESSENGER_TRANSPORT_DSN |
order, stock,
shipping, billing
|
nouvel utilisateur RabbitMQ, rechargement, puis suppression de l'ancien |
PAYMENT_API_KEY |
billing |
nouvelle clé chez le prestataire, puis révocation |
MERCURE_PUBLISHER_TOKEN |
order, stock,
shipping
|
nouveau jeton signé dans la zone avant l'échéance |
MERCURE_SUBSCRIBER_SECRET |
order, hub Mercure |
changé dans les deux à la fois |
La zone a aussi ses propres secrets : la clé d'API du job de promotion et le secret d'authentification de Dokploy. S'y ajoute la clé privée qui signe les jetons Mercure.
2. Durcir chaque Dokploy
Dokploy dérive sa clé de chiffrement de son secret
d'authentification, ou d'une clé dédiée
(ENCRYPTION_KEY_FILE). Une installation
antérieure à la version 0.29.3 peut encore utiliser un
secret codé en dur dans Dokploy : la clé est alors publique.
# Sur un manager : le secret d'authentification doit venir d'un secret Docker.
docker service inspect dokploy \
--format '{{range .Spec.TaskTemplate.ContainerSpec.Env}}{{println .}}{{end}}' \
| grep -oE '^(BETTER_AUTH_SECRET|ENCRYPTION_KEY)(_FILE)?='
La sortie doit montrer une ligne
BETTER_AUTH_SECRET_FILE=. Sans elle, Dokploy
écrit au démarrage la commande de migration à lancer.
En version 0.30.8, changer ce secret rend illisibles les
variables déjà chiffrées, et un déploiement part alors sans
elles (issue #4833). Avant de migrer, ajoutez l'ancienne clé dérivée au
fichier /etc/dokploy/encryption.key, que
Dokploy essaie en dernier recours. Ensuite :
- chaque variable réenregistrée après la migration, puis ce fichier supprimé : une valeur reste en clair, ou sous l'ancienne clé, jusqu'à sa prochaine écriture ;
- aucun secret dans un fichier monté (« File Mount ») : la base ne le chiffre pas, ni le mot de passe d'une base gérée par Dokploy ;
- aucun rollback par registre : chaque instantané garde les variables en clair dans la base ;
- des sauvegardes gardées dans la zone, sans l'option « Include encryption key », cochée par défaut : la clé est conservée à part, hors ligne.
3. Ranger chaque secret dans son service
Les paramètres partagés, comme MERCURE_URL,
vont au niveau de l'environnement ; les secrets, au niveau
du service qui les consomme. Un service ne reçoit que ses
variables, plus celles qu'il cite par
${{environment.NOM}}.
Les valeurs secrètes se saisissent dans la zone. La forge
n'en connaît que les noms, dans
services/billing/env.keys. Le job de promotion
les vérifie une fois le digest jugé nouveau, avant
application.update :
# Extrait de promote.sh, avant application.update : APP contient la réponse de application.one.
while IFS= read -r NAME || [[ -n "${NAME}" ]]; do
jq -e --arg name "${NAME}" '(.env // "") | split("\n") | any(startswith($name + "=") and length > ($name | length) + 1)' \
<<<"${APP}" >/dev/null || { echo "Variable absente ou vide : ${NAME}" >&2; exit 1; }
done <"${CLONE}/services/${SERVICE}/env.keys"
4. Lire le secret dans Symfony
Pour Symfony, le secret est une variable d'environnement comme une autre :
# config/services.yaml (service billing)
services:
App\Payment\PaymentClient:
arguments:
$apiKey: '%env(PAYMENT_API_KEY)%'
Le paramètre du constructeur porte
#[\SensitiveParameter], qui le masque dans la
trace de cet appel.
5. Faire tourner un secret
- Créer une nouvelle clé chez le prestataire, sans révoquer l'ancienne.
-
La saisir dans les variables de
billing, dans le Dokploy de production. - Cliquer sur « Reload » : une variable n'est lue qu'au démarrage du conteneur.
- Vérifier le service, puis les paiements.
- Révoquer l'ancienne clé.
« Reload » (API application.reload) met à jour
le service Swarm sans créer de déploiement : le job de
promotion ne relance rien.
Swarm démarre la nouvelle tâche avant d'arrêter l'ancienne : les deux clés servent un instant. Si la mise à jour échoue, il revient à la spécification précédente, ancienne clé comprise. D'où cette vérification avant de révoquer :
# Sur un manager : « completed », pas « rollback_completed ».
docker service inspect shop-billing --format '{{.UpdateStatus.State}}'
docker service ps shop-billing --filter desired-state=running
Si les workers Messenger de billing forment une
autre application Dokploy, la rotation met aussi à jour et
recharge leurs variables.
6. Tracer les accès
Les connexions aux nœuds Swarm et l'appartenance au groupe
docker sont journalisées et revues.
Quand un audit exige un journal des lectures, nous recommandons de passer au coffre de la zone : la variable ne contient plus qu'une référence.
# Dokploy de production, service billing : une référence, pas une valeur.
PAYMENT_API_KEY=${{vault.prod-openbao.shop/billing:payment_api_key}}
# policies/dokploy-shop.hcl (coffre de production) : lecture seule.
path "secret/data/shop/*" {
capabilities = ["read"]
}
Aucun dispositif d'audit n'est activé par défaut. Une fois activé, OpenBao enregistre chaque lecture, valeurs hachées, et refuse toute requête qu'aucun ne peut enregistrer.
Ce journal nomme le jeton de Dokploy, pas une personne. Les
journaux d'audit de Dokploy (Enterprise) disent qui a
déployé ; le terminal et
docker service inspect échappent aux deux.
Avec la configuration par défaut d'OpenBao, le jeton de Dokploy expire au plus tard après 32 jours, et Dokploy ne le renouvelle pas. Il entre donc dans l'inventaire des rotations.
Checklist
- Aucun secret de production dans les secrets ou les variables de la forge.
- Aucune clé d'API Dokploy dans la forge, aucune connexion de la CI vers les environnements.
- Un Dokploy par environnement, et aucun compte de développeur sur celui de production.
- Dokploy 0.29.12 ou plus, son secret d'authentification dans un secret Docker, migré sans perdre l'ancienne clé.
-
Les secrets au niveau du service, avec des valeurs
différentes en
staging. - Les noms des variables attendues dans la forge, vérifiés par le job de promotion.
- Aucun secret dans un fichier monté, aucun rollback par registre, des sauvegardes sans leur clé.
- Une procédure de rotation testée pour chaque secret, rechargement compris.
- Les accès aux nœuds journalisés, et un coffre dès qu'un audit exige un journal des lectures.
Sources
Versions relevées le 3 octobre 2026 : Dokploy 0.30.8, Docker Engine 29.8.2 et OpenBao 2.7.1.
- GitHub, Secure use reference et Using secrets in GitHub Actions.
- GitLab, CI/CD variables.
- Microsoft, Variable groups et Secret variables.
- OWASP, Secrets Management Cheat Sheet.
- Dokploy, versions 0.29.12 (chiffrement des variables, PR #4789), 0.30.0 (fournisseurs de secrets) et 0.30.8, issue #4833 (changement du secret d'authentification).
- Dokploy, Environment Variables, Secrets Providers, HashiCorp Vault / OpenBao, Permissions, Audit Logs et Backups.
- Dokploy, code de la version 0.30.8 : chiffrement, variables du service Swarm, résolution des variables, API des applications et accès au terminal.
- Dokploy, code de la version 0.30.8 : fournisseurs de secrets, rollbacks, fichiers montés et sauvegarde.
- Docker, Manage sensitive data with Docker secrets, Docker daemon attack surface et Linux post-installation steps.
- Docker, docker service inspect, docker service update, docker service ps et états d'une mise à jour.
- OpenBao, audit, tokens et site du projet.
- Symfony, secrets.