The Twelve-Factor App says store config in environment variables. That was good advice in 2011. For secrets in 2026, we need more.
Environment variables work until they don’t: they appear in process listings, get logged accidentally, persist in shell history, and lack rotation mechanisms. For API keys and database credentials, we need purpose-built solutions.
The Problems with ENV Vars for Secrets
Accidental exposure:
| |
No rotation: Changing a secret means redeploying every service that uses it. During an incident, that’s too slow.
No audit trail: Who accessed what secret, when? ENV vars don’t tell you.
Sprawl: Secrets end up in .env files, CI/CD configs, Docker Compose files, Kubernetes manifests—impossible to track.
The Secrets Management Stack
Modern secrets management has three layers:
1. Secrets Store (Source of Truth)
A centralized, encrypted store with access control:
HashiCorp Vault — The gold standard. Dynamic secrets, leasing, revocation, multiple auth backends.
AWS Secrets Manager / Parameter Store — Native to AWS, integrated with IAM.
Azure Key Vault / GCP Secret Manager — Cloud-native options.
Doppler, 1Password Secrets — SaaS options for smaller teams.
2. Injection Mechanism (Getting Secrets to Apps)
How secrets move from store to application:
Init containers — Fetch secrets at startup, write to shared volume:
| |
Sidecar pattern — Vault Agent runs alongside your app, handles auth and renewal:
| |
Direct SDK integration — App fetches secrets at runtime:
| |
3. Secret Rotation
Secrets should rotate automatically. Static credentials are a liability.
Dynamic secrets — Vault generates credentials on-demand:
| |
The credentials are created when requested, have a TTL, and are automatically revoked. If leaked, they expire before an attacker can use them.
Automated rotation — AWS Secrets Manager can rotate RDS credentials automatically:
| |
Implementation Patterns
Pattern 1: External Secrets Operator (Kubernetes)
Sync secrets from external stores into Kubernetes:
| |
Kubernetes Secret stays in sync. Apps consume it normally. Rotation happens automatically.
Pattern 2: Application-Level Caching
Fetch secrets at startup, cache with TTL, refresh before expiry:
| |
Pattern 3: Sealed Secrets for GitOps
Store encrypted secrets in Git, decrypt in-cluster:
| |
This lets you keep secrets in version control without exposing them.
Common Mistakes
Hardcoded fallbacks:
| |
Logging secrets:
| |
Overly broad access: Every service shouldn’t access every secret. Scope permissions tightly.
No rotation plan: If you can’t rotate a secret in under an hour, you’re vulnerable.
Secrets in container images:
| |
The Minimum Viable Setup
Not ready for Vault? Start here:
- Use a secrets manager — Even AWS Parameter Store (SecureString) beats .env files
- Inject at runtime — Never bake secrets into images or commits
- Audit access — Know who can read which secrets
- Plan for rotation — Design apps to handle secret changes without restart
| |
The Mental Model
Think of secrets like physical keys:
- You don’t leave copies everywhere
- You track who has them
- You change locks when keys are lost
- You give people only the keys they need
Environment variables are like writing the key code on a sticky note. A secrets manager is like a proper key cabinet with logs and access control.
The Twelve-Factor advice was “config in env vars.” The modern interpretation: reference secrets via env vars, but store them in a proper secrets manager.
Your credentials deserve better than plaintext in a .env file.