Handling secrets for containers: environment variables, secret stores and rotation
In short: Containers need secrets, but images and environment variables are poor places to keep them. Prefer a central secret store, deliver secrets at runtime as files or through a client library, and use workload identity to authenticate to the store. Rotate regularly with a create, deploy, verify, revoke cycle.
The problem
A container usually needs at least one secret: a database password, an API key, a TLS private key. Because containers are built once and run many times in many places, it is tempting to bake secrets into the image or a Compose file. Both spread the secret to everyone who can read the image or repository. A secret should be supplied at runtime, to the one workload that needs it, and be easy to replace.
Options compared
| Method | Pros | Cons |
|---|---|---|
| Baked into image | None worth having | Anyone with the image has the secret; cannot rotate without rebuild |
| Environment variable | Simple, supported everywhere | Shown by docker inspect, crash dumps and debug pages; inherited by child processes |
| Mounted file (Docker or Kubernetes secret) | Not in process environment; file permissions apply | Platform storage must itself be protected |
| External secret store | Central audit, access policies, rotation, dynamic secrets | Extra dependency to run and make highly available |
Environment variables are not forbidden, and many frameworks expect them. If you use them, populate them from a secret store at start-up, keep debug endpoints off in production and make sure logs and error reports do not print the environment.
Secret stores in practice
Managed services such as AWS Secrets Manager, Azure Key Vault and Google Secret Manager, or self-hosted HashiCorp Vault or OpenBao, hold secrets encrypted, control access with policies and log every read. The container authenticates to the store using its workload identity, so you avoid the "secret zero" problem of needing a password to fetch your passwords.
In Kubernetes, common integrations are the Secrets Store CSI Driver, which mounts secrets from the store as files in the pod, and the External Secrets Operator, which syncs them into Kubernetes Secret objects. With Docker Swarm or Compose, secrets are mounted as files under /run/secrets/.
services:
api:
image: registry.example.com/shop/api@sha256:<digest>
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
The application then reads /run/secrets/db_password. Keep the local secrets folder out of git with .gitignore.
Rotation and dynamic secrets
Rotation limits how long a leaked secret stays useful. Plan it so there is a short overlap: create the new credential, roll workloads onto it, confirm nothing still uses the old one, then revoke it. Applications should re-read secrets on change or restart cleanly when told to.
Better still are dynamic secrets: the store creates a unique, short-lived database user for each workload and removes it automatically when the lease expires. A leak then exposes very little.
Tip: add secret scanning (for example Gitleaks or your platform's built-in scanning) to pre-commit hooks and CI so mistakes are caught before they leave a laptop.
Practice
Why is an environment variable a weaker place for a secret than a mounted file?
What is the "secret zero" problem and how do workload identities help?
Put the steps of safe rotation in order.
Key takeaways
- Never bake secrets into images or commit them to git.
- Environment variables are convenient but leak easily.
- Use a central secret store and deliver secrets at runtime.
- Authenticate to the store with workload identity.
- Rotate regularly; prefer short-lived dynamic secrets.
Original content written by Mohamed Fayas for FM TecHub. © 2026 FM TecHub. You're welcome to share a link to this page; please don't copy the text elsewhere without permission.