Initializing Enclave...

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 create rejects any secret name containing characters outside [a-zA-Z0-9._-] — spaces, slashes, @, $, #, colons, and other shell-special chars all trigger invalid 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 create command 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:

  1. Deployment pipeline stall. A single bad secret name in a docker-stack.yml or 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.

  2. 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 in docker inspect output, process listings, and CI logs.

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

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

Related Diagnostics

"Part of the Syntax Utility Matrix."

View all 153 Syntax Tools →