The Death of .env Files: Automated Secret Rotation with Terraform
Hardcoded secrets in CI/CD variables are a compliance failure waiting to happen. A walkthrough of AWS Secrets Manager rotation, codified in Terraform.
A .env file in a repo is the most common SOC2 finding I see. A .env file in a CI variable is the second. Both share the same root cause: a secret with no expiry, no audit trail, and no owner.
Secrets Manager + Terraform fixes all three in about 40 lines of HCL.
The Terraform module
resource "aws_secretsmanager_secret" "db" {
name = "prod/app/db"
kms_key_id = aws_kms_key.app.arn
recovery_window_in_days = 7
}
resource "aws_secretsmanager_secret_rotation" "db" {
secret_id = aws_secretsmanager_secret.db.id
rotation_lambda_arn = aws_lambda_function.rotate_db.arn
rotation_rules {
automatically_after_days = 30
}
}
The Lambda is one of AWS's published rotation templates — for RDS, you don't even write it. Drop in the SAR app, point it at your secret, done.
What the app gets
The app NEVER holds the password. It holds an IAM role. On startup (or per request, if you're paranoid) it calls secretsmanager:GetSecretValue. The SDK caches the value for the connection pool's lifetime. On rotation, the new version is published with AWSCURRENT, the old one becomes AWSPREVIOUS for a grace window, and connections drain to the new secret without downtime.
The audit answers
- "How often are credentials rotated?" → "Every 30 days, enforced by Terraform." Show the
automatically_after_daysline. - "Who has access to production secrets?" → "Whoever the resource policy on
prod/app/dbgrantssecretsmanager:GetSecretValueto — five IAM roles, listed here." - "When was this secret last accessed?" → CloudTrail
GetSecretValueevents, queried byeventSource = secretsmanager.amazonaws.com.
The migration play
- For each
.enventry: create the Secrets Manager secret in Terraform, populate the initial value out-of-band, attach the rotation Lambda. - Update the app to fetch from Secrets Manager (cache for 5 minutes; that's safe).
- Delete the CI variable. Delete the
.env. Add agit secretspre-commit hook so nobody re-adds one. - Run
terraform planquarterly to prove the rotation cadence hasn't drifted.
The goal isn't "we have a secrets manager." Plenty of compromised companies had one. The goal is "no human ever sees a production credential, and the audit trail proves it."
Further reading: Secrets Manager rotation.
Closing thought
Every secret on a laptop is a secret one laptop-loss away from incident response. The work is not glamorous — it is rotation, IAM, audit log, repeat — but it is the difference between an attestable control and a hopeful one. Get there once, automate it, and stop relitigating it on every audit.
Practical pitfalls to avoid
- Caching secrets longer than the rotation window
- Storing the bootstrap credential in CI variables instead of OIDC
- Granting
secretsmanager:GetSecretValueto a wildcard resource - Skipping the alarm on failed rotations — silent failure is the worst failure
Ask AI About the Author
Open this query in ChatGPT, Claude, or Perplexity.
Comments
Comments are open to confirmed email subscribers. Use the email you subscribed with. To edit a comment, delete it and post a new one.
Get new field notes by email
Field notes from someone who ships before they write about it. Sovereign AI, AI-SDLC, DevOps, and what 59 production deployments teach you. No spam. Unsubscribe anytime.