Every system has secrets — database passwords, API keys, signing keys, tokens. Where they live, who can read them, and how often they rotate is one of the highest-leverage security decisions you'll make. Get it wrong and a single leaked file becomes a full breach.
← Back to Security.env.example, not in tests. Once it lands in Git history it must be considered leaked forever.| Tool | Sweet spot |
|---|---|
| HashiCorp Vault | Self-hosted gold standard. Dynamic secrets, leases, transit engine for encrypt-as-a-service. |
| AWS Secrets Manager / SSM Parameter Store | Native to AWS workloads; rotation hooks for RDS and friends. |
| GCP Secret Manager / Azure Key Vault | Cloud-native equivalents. Tight IAM integration. |
| Doppler / 1Password Secrets Automation / Infisical | Hosted, dev-friendly, multi-environment. Good for small/mid teams. |
| Kubernetes Secrets (with KMS or Sealed Secrets) | K8s-native. Plain Secrets are base64, not encrypted — pair with KMS at rest, or use Sealed Secrets / SOPS / External Secrets Operator. |
| SOPS + KMS | Encrypted files in Git, decrypted at deploy. Good middle ground when a vault is overkill. |
| Cloud KMS (AWS KMS, GCP KMS, Azure Key Vault) | Not for arbitrary secrets — for encrypting/decrypting things, including other secrets. The root of trust below the vault. |
Default for small teams: your cloud's secret manager. Default for a self-hosted stack: Vault.
Whichever method, the principle is the same: the secret enters the process at runtime, not at build time. Never bake a secret into a container image.
The single biggest improvement in secrets hygiene over the last few years: workload identity federation. Instead of giving CI a static AWS access key, the CI provider issues a short-lived OIDC token that the cloud trades for a temporary role.
If you still have static cloud keys in CI in 2026, that's the highest-impact thing to fix this quarter.
gitleaks, detect-secrets, trufflehog. Stop the commit at the developer's machine.Rotation isn't only for incidents — schedule it. If your secret hasn't been rotated in years, the only honest thing to assume is that it's been leaked at some point.
.env "by accident." Add it to .gitignore and template .env.example with placeholders only.| Secret | Lifetime | Storage | Delivered as |
|---|---|---|---|
| Postgres connection | Dynamic, 1h leases via Vault | Vault DB engine | Sidecar-rendered file |
| Redis password | Static, rotated quarterly | Cloud secret manager | Env var at start |
| JWT signing key | 30 days, dual-key for rollover | Vault transit engine (signs without releasing key) | Vault SDK at runtime |
| SMTP credentials | Static, rotated yearly | Cloud secret manager | Env var |
| CI → cloud deploy | ~15 min per job | Workload identity (OIDC) | Token exchange, no static key |
| 3rd-party geo-IP API key | Static, rotated quarterly | Cloud secret manager | Env var |
Notice: nothing in source, nothing baked in the image, the highest-blast-radius secret (DB) is dynamic and short-lived, and the JWT key never leaves Vault — the app asks Vault to sign on its behalf.