Your startup just got its first SOC 2 audit.
The auditor asks: "Where are your database passwords, API keys, and service tokens stored?"
Your senior engineer goes quiet.
Turns out half of them are in .env files committed to git 18 months ago. Three are hardcoded in Lambda environment variables. One is in a Slack message from 2023.
You have 6 services in production, 4 environments, and zero rotation policy.
Here's the setup:
• NestJS API → Postgres (password in env var)
• NestJS API → Stripe (API key in env var)
• Background workers → SQS, S3 (AWS credentials in env var)
• 3rd-party webhooks → HMAC secrets in env var
• Zero rotation. Zero audit trail. Zero centralized access control.
You need to fix this. And you can't take downtime.
A) Move everything to AWS Secrets Manager — SDK calls at runtime, IAM controls access, auto-rotation built in.
B) Use HashiCorp Vault — dynamic secrets, fine-grained policies, works across any cloud or on-prem.
C) Use environment variables injected at deploy time via CI/CD — secrets stored in GitHub Actions / GitLab CI secrets vault, never touch disk.
D) Encrypt secrets with KMS and store ciphertext in your own database — decrypt at runtime, full control.
All four are used in production at real companies.
Pick one — A, B, C, or D — and tell me why. I'll drop the full breakdown in the comments.
If your team is having this argument right now, share this post. Someone needs to see it.
Drop your answer 👇
Top comments (4)
C — CI/CD Injected Env Vars (partially right, wrong scope)
GitHub Actions / GitLab CI secrets are the right place for build-time and deploy-time secrets — Docker Hub creds, Terraform tokens, deploy keys.
They're wrong for runtime secrets — the DB password your app needs at 3 AM, the Stripe key for a live webhook. Those get injected as env vars into ECS task definitions or Lambda config — harder to rotate atomically, no audit trail on runtime access.
C complements A or B. It doesn't replace them.
A — AWS Secrets Manager (correct)
You're already AWS-native (Lambda, SQS, S3). Secrets Manager gives you:
• Centralized encrypted storage (KMS-backed)
• IAM-based access control per secret per service
• Native auto-rotation for RDS + custom Lambda rotators for everything else
• Every access logged in CloudTrail → exactly what your SOC 2 auditor wants
Migration is zero-downtime: swap env vars for getSecretValue() calls, deploy, rotate the old creds after confirming the new ones work. Cost: ~$0.40/secret/month. For 20 secrets that's $8/month.
B — HashiCorp Vault (senior engineer trap)
Vault is more powerful — dynamic secrets (a fresh Postgres credential per-request that expires in 1h) is one of the most elegant patterns in production infra.
But Vault is infrastructure you own and operate. You need a Raft cluster (3+ nodes), a storage backend, unsealing procedures, DR planning, and an ops team that knows Vault. If Vault goes down, secrets become inaccessible — services fail.
When Vault wins: Multi-cloud, on-prem requirements, dynamic secrets at scale, or you have a dedicated infra team. If you're AWS-native under 50 engineers — Secrets Manager first, always.
D — DIY KMS + own database (wrong)
This is reinventing Secrets Manager badly. You now own: encryption key management, rotation logic, access control, audit logging, and a database that itself needs credentials to connect to.
The meta-problem: what secret protects the secret store? You end up with a bootstrapping problem that managed secret stores are specifically designed to solve. This is what teams built in 2012 before managed options existed.