Managing Secrets in Terraform Without Leaking Them
Terraform writes every secret it touches into state in plaintext. Here's how I keep credentials out of code and state, and reference them safely instead.
- #terraform
- #secrets
- #security
- #vault
- #state
- #devops
The first thing every Terraform newcomer learns the hard way: that database password you passed as a variable is now sitting in your state file in plaintext. Terraform must store the values it manages, and it doesn’t encrypt them inside state. In 25 years of managing estates, leaked secrets in state and Git history are among the most common — and most avoidable — security failures I see.
Here’s how I handle secrets so they don’t end up where attackers can read them.
Understand the two leak points
Secrets in Terraform leak in two places:
- Source code — a hardcoded password committed to Git lives in history forever, even after you delete it.
- State — any secret Terraform manages is written to state in plaintext, regardless of how you passed it in.
You have to defend both. Keeping secrets out of code is necessary but not sufficient; if the value flows through Terraform, it lands in state too.
Never put secrets in code
This is non-negotiable. No passwords, no tokens, no keys in .tf files or .tfvars committed to Git. Mark sensitive variables so Terraform redacts them from plan output:
variable "db_password" {
type = string
sensitive = true
}
sensitive = true stops the value appearing in CLI output and PR comments. It does not encrypt it in state — a point people constantly misunderstand. It’s a display guard, not a storage guard.
Pull secrets from a real secrets manager
The right pattern is to never let Terraform own the secret. Have it fetch the secret at runtime from a dedicated store:
data "aws_secretsmanager_secret_version" "db" {
secret_id = "prod/checkout/db"
}
resource "aws_db_instance" "checkout" {
password = data.aws_secretsmanager_secret_version.db.secret_string
}
With Vault it’s the same shape:
data "vault_generic_secret" "db" {
path = "secret/prod/checkout"
}
The secret is created and rotated outside Terraform, in the secrets manager. Terraform reads it. Caveat: the value still passes through state via the data source, so encrypt and lock down state regardless.
Better still: let the resource generate its own secret
The cleanest pattern is when the secret never touches Terraform at all. Have the resource generate it and store it directly in the secrets manager:
resource "aws_db_instance" "checkout" {
manage_master_user_password = true
}
Here RDS generates and rotates the master password into Secrets Manager itself. Terraform never sees the plaintext, so it never lands in state. When the platform offers this, take it every time.
Lock down state like it contains secrets — because it does
Since state holds plaintext secrets, treat the state file as a secret:
- Encrypt at rest (S3 SSE, or a backend with native encryption).
- Restrict read access to only the CI role and break-glass admins.
- Never commit state, never email it, never drop it in a ticket.
I’ve seen more breaches from a state file pasted into a Slack channel than from any clever exploit.
Where AI helps — and where it’s dangerous
AI is excellent at auditing for leaks. I’ll point it at a module and ask: “Find any place a secret could be hardcoded, logged, or exposed in output.” It reliably flags a missing sensitive = true or a password interpolated into a non-sensitive output. Our Code Review tool runs this as a structured scan on every diff, and I keep audit-style Terraform prompts for it.
The danger: never paste real secrets into an AI chat. Treat the model like any third party — it gets your config structure, never your live credentials. Sanitize before you share.
The takeaway
Secrets in Terraform are a two-front problem: keep them out of code and out of state. Mark variables sensitive, fetch from a real secrets manager, prefer resources that generate and store their own credentials, and lock down state because it holds plaintext no matter what. Treat every state file as a secret and you close the most common door attackers walk through.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.