Home DevOps Docker Secrets Management: Stop Hardcoding Credentials in Production
Advanced 3 min · July 11, 2026

Docker Secrets Management: Stop Hardcoding Credentials in Production

Docker secrets management explained with production patterns.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
Production
production tested
July 11, 2026
last updated
258
articles · all by Naren
Before you start⏱ 35 min
  • Docker basics (images, containers, services)
  • Docker Swarm or Kubernetes fundamentals
  • Familiarity with environment variables in Docker
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer

Use Docker's built-in docker secret create for Swarm services, or mount secrets as files via bind mounts with restricted permissions. Never pass secrets as environment variables in production — they're visible in process lists and logs.

✦ Definition~90s read
What is Docker Secrets Management?

Docker Secrets Management is the practice of securely storing and injecting sensitive data (passwords, API keys, TLS certificates) into containers at runtime, avoiding hardcoded values in images or environment variables that leak via docker inspect.

Think of Docker secrets like a hotel safe deposit box.
Plain-English First

Think of Docker secrets like a hotel safe deposit box. You (the container) get a key to open the box when you check in, but the box's contents are never written on the key itself. If someone steals the key, they still can't read the contents without physically opening the box. Similarly, Docker secrets are stored encrypted at rest and only decrypted in memory inside the container that needs them.

I've seen a startup's entire database credentials leaked because someone ran docker inspect on a running container. The password was in an environment variable. That's not a rookie mistake — it's a design flaw. Docker Secrets Management exists precisely to prevent this. It's not about convenience; it's about not getting pwned at 3 AM when an attacker enumerates your containers. After this article, you'll be able to design a secret delivery pipeline that survives audits, rotates keys without downtime, and doesn't make you cringe when ops asks for the secret inventory.

Why Environment Variables Are a Security Nightmare

Environment variables are the default way to pass configuration to containers. They're easy, they're documented everywhere, and they're wrong for secrets. Here's why: any process with access to /proc/<pid>/environ can read them. That includes docker inspect, kubectl exec, and any compromised container on the same host. Worse, env vars leak into logs, error reports, and core dumps. I've personally debugged a case where a Python traceback printed the entire DB password because it was in the environment. The fix? Never put secrets in env vars in production. Use Docker secrets or a vault.

env_var_leak.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
// io.thecodeforge — DevOps tutorial

# Run a container with a secret as env var
docker run -d --name leaky -e DB_PASSWORD=supersecret alpine sleep 3600

# Inspect the container — password visible
docker inspect leaky | grep DB_PASSWORD
# Output: "DB_PASSWORD=supersecret"

# Or read from /proc
docker exec leaky cat /proc/1/environ | tr '\0' '\n' | grep DB_PASSWORD
# Output: DB_PASSWORD=supersecret
Output
DB_PASSWORD=supersecret
DB_PASSWORD=supersecret
Production Trap:
Even if you delete the env var after reading it, the process's initial environment is captured in /proc. There's no safe way to use env vars for secrets.

Docker Swarm Secrets: The Built-In Solution

Docker Swarm has native secret management. Secrets are encrypted at rest in the Swarm store and only decrypted when a container that needs them is scheduled. They're mounted as files in /run/secrets/ by default, with in-memory tmpfs. No disk writes. No env var leaks. The flow: docker secret create stores the secret, then you grant access to services via --secret. The container reads the file at startup. Rotation requires updating the secret and redeploying the service. For zero-downtime rotation, you can mount multiple versions and have the app watch for changes.

swarm_secret_setup.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge — DevOps tutorial

# Create a secret from stdin
echo "my-db-password" | docker secret create db_password -

# Verify
docker secret ls

# Deploy a service with the secret
docker service create --name web \
  --secret db_password \
  --publish 80:80 \
  nginx:alpine

# Inside the container, the secret is at /run/secrets/db_password
docker exec $(docker ps -q) cat /run/secrets/db_password
# Output: my-db-password
Output
my-db-password
Senior Shortcut:
Use docker secret create with --label to track metadata like rotation date or owner. It's a lifesaver when auditors ask for an inventory.

Kubernetes Secrets: More Power, More Pitfalls

Kubernetes Secrets are similar but more flexible — and more dangerous. By default, Secrets are stored unencrypted in etcd. You must enable encryption at rest. They can be mounted as volumes or injected as env vars (don't do the latter). The real power is in integration with external vaults like HashiCorp Vault via CSI drivers or sidecars. For production, never use plain Kubernetes Secrets for anything sensitive. Always enable encryption and use RBAC to restrict access. The classic mistake: storing a cloud API key in a Secret and then accidentally committing the YAML to git.

k8s_secret.yamlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// io.thecodeforge — DevOps tutorial

apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:
  password: bXktZGItcGFzc3dvcmQ=  # base64 of 'my-db-password'
---
apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  containers:
  - name: web
    image: nginx:alpine
    volumeMounts:
    - name: secrets
      mountPath: /etc/secrets
      readOnly: true
  volumes:
  - name: secrets
    secret:
      secretName: db-credentials
Never Do This:
Setting envFrom with a Secret. It creates env vars that leak via /proc and kubectl exec. Always mount as a volume.

Secret Rotation Without Downtime

Rotating secrets is where most teams fail. The naive approach: update the secret, restart all containers. That causes downtime. Better: mount the secret as a file and have the application watch for file changes (e.g., inotify). When the file changes, reload the connection. For databases, use a connection pool that supports credential refresh (like HikariCP with a DataSource that reads from a file). In Swarm, you can update a secret and redeploy the service with --update-delay for rolling updates. In Kubernetes, use a controller that watches the Secret and triggers a rolling restart via annotation change.

secret_watcher.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// io.thecodeforge — DevOps tutorial

import os
import time
import signal

SECRET_PATH = '/run/secrets/db_password'

def load_secret():
    with open(SECRET_PATH) as f:
        return f.read().strip()

def reload_handler(signum, frame):
    global db_password
    db_password = load_secret()
    print(f"Secret reloaded at {time.ctime()}")

# Register SIGHUP handler for manual reload
signal.signal(signal.SIGHUP, reload_handler)

# Initial load
db_password = load_secret()
print(f"Initial password: {db_password}")

# Simulate main loop
while True:
    time.sleep(10)
    # In production, use inotify or watchdog library for file change detection
Output
Initial password: my-db-password
Secret reloaded at Mon Jan 15 12:00:00 2024
Interview Gold:
How do you rotate a database password without dropping connections? Answer: Use a connection pool that supports credential refresh, or implement a two-phase rotation where both old and new passwords are accepted during a transition window.

External Vault Integration: HashiCorp Vault with Docker

For serious production environments, built-in secrets managers are too limited. You need audit logs, dynamic secrets (short-lived credentials), and fine-grained access control. HashiCorp Vault is the gold standard. Integrate with Docker via the Vault agent sidecar pattern: a container that authenticates to Vault, retrieves secrets, and writes them to a shared volume. The application reads from that volume. Vault handles rotation automatically. The downside: complexity. You now manage Vault itself. But for compliance-heavy environments (PCI-DSS, HIPAA), it's non-negotiable.

docker-compose.vault.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// io.thecodeforge — DevOps tutorial

version: '3.8'
services:
  vault-agent:
    image: vault:latest
    command: agent -config=/etc/vault/config.hcl
    volumes:
      - shared-secrets:/secrets
      - ./vault-config:/etc/vault
    environment:
      VAULT_ADDR: https://vault.example.com
  app:
    image: myapp:latest
    volumes:
      - shared-secrets:/secrets:ro
    depends_on:
      - vault-agent
volumes:
  shared-secrets:
Production Trap:
Vault agent can become a single point of failure. Always run at least two replicas and use a load balancer. Also, ensure the shared volume is tmpfs, not a network filesystem — latency kills.

When Not to Use Docker Secrets

Docker secrets are overkill for local development. Use .env files or docker-compose's env_file — just don't commit them to git. For single-node deployments, bind-mounting a secrets directory with restricted permissions is simpler and equally secure. Also, if your orchestrator doesn't support secrets (e.g., plain Docker Compose without Swarm), you're stuck with workarounds. In those cases, use a vault sidecar or encrypt the env var with a key that the app decrypts at startup. The key itself must be stored securely — a chicken-and-egg problem that vault solves.

local_secret_mount.shBASH
1
2
3
4
5
6
7
8
9
10
// io.thecodeforge — DevOps tutorial

# For local dev, just mount a file with restricted permissions
mkdir -p ./secrets
echo "dev-password" > ./secrets/db_password
chmod 0400 ./secrets/db_password
chown 1000:1000 ./secrets/db_password

docker run -v $(pwd)/secrets:/run/secrets:ro alpine cat /run/secrets/db_password
# Output: dev-password
Output
dev-password
Senior Shortcut:
For local dev, use a .secrets directory with a .gitignore entry. Add a secrets.example file with placeholder values for onboarding.
● Production incidentPOST-MORTEMseverity: high

The 4GB Container That Kept Dying

Symptom
A microservice handling payment processing crashed every 6 hours with OOMKilled. Logs showed 'Error: Connection pool exhausted' before crash.
Assumption
The team assumed a memory leak in the application code and spent days profiling heap dumps.
Root cause
The database password was stored as an environment variable. The connection pool library was reading the env var on every connection attempt, but the secret was rotated externally, causing the pool to hold stale connections that failed authentication. The retry logic spawned new connections without closing old ones, eventually exhausting memory.
Fix
Switched to Docker secrets mounted as a file. The application read the secret once at startup and cached it. Rotation now requires a restart, but the pool is stable and predictable.
Key lesson
  • Never let your application re-read secrets from environment variables on every request — it's a performance and reliability trap.
Production debug guideSystematic recovery paths for the failure modes engineers actually hit.3 entries
Symptom · 01
Container fails to start with 'secret not found'
Fix
1. Run docker secret ls to verify secret exists. 2. Check service definition for correct secret name. 3. Ensure service is in the same Swarm as the secret.
Symptom · 02
Application reads empty secret file
Fix
1. Verify secret file size: docker exec <container> ls -la /run/secrets/. 2. Check if secret was created with trailing newline — use printf instead of echo. 3. Ensure file permissions allow read by the runtime user.
Symptom · 03
Secret rotation not taking effect
Fix
1. Confirm secret was updated: docker secret inspect <secret>. 2. Redeploy the service: docker service update --secret-rm <old> --secret-add <new> <service>. 3. For zero-downtime, use rolling update with --update-delay.
★ Docker Secrets Management Triage Cheat SheetFirst-response commands for when things go wrong — copy-paste ready.
`secret not found` error
Immediate action
Check if secret exists in Swarm
Commands
docker secret ls
docker service ps <service> --no-trunc | grep secret
Fix now
Create the secret: echo 'password' | docker secret create <name> -
Empty secret file inside container+
Immediate action
Check file size and content
Commands
docker exec <container> ls -la /run/secrets/<name>
docker exec <container> cat /run/secrets/<name> | wc -c
Fix now
Recreate secret without trailing newline: printf 'password' | docker secret create <name> -
Permission denied reading secret+
Immediate action
Check file permissions and ownership
Commands
docker exec <container> ls -la /run/secrets/
docker exec <container> id
Fix now
Set secret with specific uid/gid: docker service create --secret source=<name>,uid=1000,gid=1000,mode=0400 ...
Secret not updated after rotation+
Immediate action
Verify secret version and redeploy
Commands
docker secret inspect <name> --format '{{.UpdatedAt}}'
docker service update --secret-rm <old> --secret-add <new> <service>
Fix now
Use docker service update --force to trigger a rolling restart
Feature / AspectEnvironment VariablesDocker Swarm SecretsKubernetes SecretsHashiCorp Vault
Encryption at restNoYes (in Swarm store)Optional (encryption config)Yes (always)
Audit trailNoNoNo (native)Yes (detailed logs)
Rotation without restartNoNo (requires redeploy)No (requires restart)Yes (dynamic secrets)
ComplexityLowMediumMediumHigh
Leak via /procYesNo (file mount)No if mounted as volumeNo (file mount)
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
env_var_leak.shdocker run -d --name leaky -e DB_PASSWORD=supersecret alpine sleep 3600Why Environment Variables Are a Security Nightmare
swarm_secret_setup.shecho "my-db-password" | docker secret create db_password -Docker Swarm Secrets
k8s_secret.yamlapiVersion: v1Kubernetes Secrets
secret_watcher.pySECRET_PATH = '/run/secrets/db_password'Secret Rotation Without Downtime
docker-compose.vault.ymlversion: '3.8'External Vault Integration
local_secret_mount.shmkdir -p ./secretsWhen Not to Use Docker Secrets

Key takeaways

1
Never use environment variables for secrets in production
they leak via /proc and docker inspect.
2
Docker Swarm secrets and Kubernetes Secrets are better, but still require careful handling for rotation and access control.
3
For zero-downtime rotation, mount secrets as files and have the app watch for changes or use a vault sidecar.
4
External vaults like HashiCorp Vault are the only way to get dynamic secrets and audit trails at scale.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does Docker Swarm handle secret encryption at rest and in transit?
Q02SENIOR
When would you choose Kubernetes Secrets over HashiCorp Vault in a produ...
Q03SENIOR
What happens when you update a Docker secret while a service is running ...
Q04JUNIOR
What is the difference between `docker secret create` and storing a secr...
Q05SENIOR
A developer stored a database password in a Kubernetes Secret and commit...
Q06SENIOR
How would you design a secret delivery system for a 500-container micros...
Q01 of 06SENIOR

How does Docker Swarm handle secret encryption at rest and in transit?

ANSWER
Secrets are encrypted using the Swarm manager's Raft log encryption (AES-GCM) at rest. In transit, they're sent over TLS between managers and workers. The decryption key is stored in the manager's memory and never written to disk.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
How do I pass secrets to a Docker container without using environment variables?
02
What's the difference between Docker secrets and Kubernetes secrets?
03
How do I rotate a Docker secret without restarting the container?
04
Can I use Docker secrets with docker-compose?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
Verified
production tested
July 11, 2026
last updated
258
articles · all by Naren
🔥

That's Docker. Mark it forged?

3 min read · try the examples if you haven't

Previous
Docker with GitLab CI/CD
32 / 32 · Docker
Next
Introduction to Kubernetes