Fixing 'invalid secret name' in Docker Secret Create: Special Character Errors Explained
Threat/Impact Level: MEDIUM | Downtime Risk: HIGH | Time to Fix: 5 mins
TL;DR
- What broke:
docker secret createrejects any secret name containing characters outside[a-zA-Z0-9._-]— spaces, slashes,@,$,#, colons, and other shell-special chars all triggerinvalid secret name. - How to fix it: Rename the secret using only alphanumerics, dots, underscores, and hyphens. Re-create the secret with the sanitized name and update all referencing service specs.
- Fast path: Use our Client-Side Sandbox below to auto-refactor this — paste your failing
docker secret createcommand and get a corrected version instantly without sending your secrets to any server.
The Incident (What Does the Error Mean?)
Raw error output from the Docker CLI or daemon log:
$ docker secret create db@prod/password ./secret.txt
Error response from daemon: invalid secret name, only [a-zA-Z0-9._-] are allowed
Or via Docker Compose / stack deploy:
failed to create secret db@prod/password: invalid secret name
Immediate consequence: The secret is never written to the Raft-encrypted store. Any service depending on that secret fails to deploy or update, stalling your rollout entirely. In a CI/CD pipeline this surfaces as a non-zero exit code that kills the deployment stage with zero rollback — the previous service version keeps running but no new config is applied.
The Attack Vector / Blast Radius
This is a naming contract violation, not a runtime security vulnerability — but the blast radius is real:
Deployment pipeline stall. A single bad secret name in a
docker-stack.ymlor Helm-equivalent compose file blocks the entire stack deploy, not just the offending service. All services in that stack that haven't started yet are orphaned.Silent config drift. Engineers working around the error often resort to embedding the credential directly in an environment variable (
-e DB_PASSWORD=s3cr3t), trading a naming error for a critical secrets exposure indocker inspectoutput, process listings, and CI logs.Automation fragility. Secret names generated programmatically from external sources (Vault paths like
secret/db@prod/password, AWS SSM paths like/prod/db/password) carry illegal characters by convention. If your automation doesn't sanitize on ingestion, every new secret provisioned from those sources will fail.Swarm Raft store integrity. While Docker validates names at the API layer before writing, repeated failed API calls from a broken automation loop can produce excessive daemon log noise and, in high-frequency scenarios, contribute to API request queuing on the manager node.
How to Fix It (The Solution)
Basic Fix
Strip or replace every character outside [a-zA-Z0-9._-]. The most common substitution is replacing / and @ with _ or -.
- docker secret create db@prod/password ./secret.txt
+ docker secret create db_prod_password ./secret.txt
- docker secret create /prod/db/conn-string ./conn.txt
+ docker secret create prod_db_conn-string ./conn.txt
After re-creating the secret, update every service that mounts it:
services:
api:
secrets:
- - db@prod/password
+ - db_prod_password
secrets:
- db@prod/password:
+ db_prod_password:
external: true
Enterprise Best Practice
Enforce a naming convention at the automation layer — never let raw Vault/SSM paths reach docker secret create unsanitized. Use a normalization function in your provisioning script:
- SECRET_NAME=$(vault kv get -field=name secret/db@prod/password)
- docker secret create "$SECRET_NAME" <(vault kv get -field=value secret/db@prod/password)
+ # Normalize: replace any char not in [a-zA-Z0-9._-] with underscore
+ RAW_NAME="db@prod/password"
+ SECRET_NAME=$(echo "$RAW_NAME" | tr -cs 'a-zA-Z0-9._-' '_' | sed 's/_$//')
+ vault kv get -field=value secret/db@prod/password | docker secret create "$SECRET_NAME" -
For Docker Compose / stack files, enforce naming via a pre-deploy linter (see CI/CD section below). In Kubernetes (if migrating), the equivalent constraint is [a-z0-9.-] max 253 chars — same sanitization pipeline applies.
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. Shell-level guard in your provisioning script (immediate, zero-dep):
validate_secret_name() {
local name="$1"
if [[ ! "$name" =~ ^[a-zA-Z0-9._-]+$ ]]; then
echo "ERROR: Invalid secret name '$name'. Only [a-zA-Z0-9._-] allowed." >&2
exit 1
fi
}
validate_secret_name "$SECRET_NAME"
Fail fast in the pipeline before the Docker API call is ever made.
2. Checkov / custom policy for docker-compose files:
Checkov does not ship a built-in rule for secret name format, so add a custom check:
# checkov/custom_checks/docker_secret_name.py
import re
from checkov.common.models.enums import CheckResult
from checkov.dockerfile.base_dockerfile_check import BaseDockerfileCheck
# For compose files use a YAML-based runner check instead;
# validate secrets keys match ^[a-zA-Z0-9._-]+$
SECRET_NAME_RE = re.compile(r'^[a-zA-Z0-9._-]+$')
Integrate into your PR pipeline: checkov -d . --external-checks-dir ./checkov/custom_checks.
3. OPA/Conftest policy for Compose/Stack YAML:
package docker.secrets
deny[msg] {
secret_name := input.secrets[name]
not regex.match(`^[a-zA-Z0-9._\-]+$`, name)
msg := sprintf("Invalid Docker secret name '%v': only [a-zA-Z0-9._-] are permitted", [name])
}
Run in CI: conftest test docker-stack.yml --policy ./policy/
4. Git pre-commit hook (fastest feedback loop):
# .git/hooks/pre-commit
grep -rE 'secret create [^a-zA-Z0-9._\-]' . && \
echo 'Invalid docker secret name detected. Aborting.' && exit 1
Catch it before it ever reaches the remote.