Secrets Management in Terraform
Terraform frequently needs sensitive values — database passwords, API tokens, TLS private keys, cloud access credentials — to provision infrastructure. The core tension is that Terraform's execution model persists nearly everything it touches: variable values, resource attributes, and computed outputs all end up in the plan file and, more importantly, in the state file. Unlike application secrets that can be injected at runtime and forgotten, Terraform secrets tend to have a much longer paper trail unless you deliberately design around it. Effective secrets management in Terraform is therefore not a single feature but a discipline that spans variable declaration, provider configuration, state storage, and CI/CD pipeline design.
Cricket analogy: Terraform persisting secrets across plan and state files is like a scorecard permanently recording a player's exact biometric data submitted for fitness testing — unlike a quick verbal fitness check, it leaves a lasting paper trail across the whole tournament record.
The sensitive Flag and Its Limits
Terraform lets you mark a variable or output as sensitive, which suppresses the value from CLI output in plans, applies, and console logs. This is a genuinely useful guardrail against accidental screen-sharing or CI log leakage, but it is a UI-layer redaction only — it does nothing to protect the value inside the state file, which stores it in plaintext by default. Treating the sensitive flag as encryption is one of the most common misconceptions among teams new to Terraform.
Cricket analogy: Marking a Terraform value sensitive is like a broadcaster blurring a player's private medical note on the TV graphic — it hides the value from the viewer's screen, but the raw document sitting in the production van's files is still fully readable.
variable "db_password" {
type = string
sensitive = true
description = "Master password for the RDS instance"
}
resource "aws_db_instance" "primary" {
identifier = "app-primary-db"
engine = "postgres"
engine_version = "15.4"
instance_class = "db.t3.medium"
username = "app_admin"
password = var.db_password
skip_final_snapshot = false
}
output "db_endpoint" {
value = aws_db_instance.primary.endpoint
sensitive = false
}Pulling Secrets from a Dedicated Secret Store
The preferred pattern is to never author secret literals in HCL or tfvars files at all. Instead, use a data source to fetch the secret at plan/apply time from a purpose-built secret manager such as AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, or GCP Secret Manager. This keeps ownership of secret rotation and access policy with a system designed for it, and it means the secret's lifecycle is decoupled from your Terraform code's lifecycle — rotating a password does not require a code change or a re-apply of unrelated resources.
Cricket analogy: Fetching a secret from Vault at apply time instead of hardcoding it is like a team pulling the day's pitch report from the groundskeeper on match morning instead of printing it in last season's handbook — updating the pitch conditions never requires reprinting the whole handbook.
data "aws_secretsmanager_secret_version" "db" {
secret_id = "prod/app/db-password"
}
resource "aws_db_instance" "primary" {
identifier = "app-primary-db"
username = "app_admin"
password = data.aws_secretsmanager_secret_version.db.secret_string
# ... other config
}Protecting the State File Itself
Even when secrets are pulled from a vault rather than hardcoded, Terraform still writes the resolved value into state, because state must record the real-world attributes of every managed resource to compute future diffs. The mitigation is defense in depth at the backend layer: use a remote backend with encryption at rest (S3 with SSE-KMS, Azure Blob with a customer-managed key, Terraform Cloud's built-in encryption), restrict IAM/RBAC access to the state bucket or workspace to a small set of principals, and enable state file access logging so reads are auditable.
Cricket analogy: Even when a secret comes from Vault, Terraform still writes the resolved password into state, so teams add defense in depth like locking the team's confidential scouting reports in a safe (encryption at rest), restricting the safe's key to the coach and captain only (IAM), and logging every time it's opened (access logging).
Never commit a .tfvars file containing plaintext secrets to version control, even a private repository. Git history retains every commit forever unless you rewrite history and force-push, and a single leaked commit can expose a credential long after the file is 'fixed.' Add *.tfvars with secret content to .gitignore and use environment variables or a secret store instead.
A useful mental model: Terraform state is not a secrets vault — it is a receipt. It records what was purchased (provisioned) so Terraform can reconcile future changes, but a receipt was never designed to be the secure custody location for the item itself.
- The sensitive attribute hides values from CLI/log output but does not encrypt them in the state file.
- Prefer pulling secrets at apply time via data sources from Vault, AWS/Azure/GCP secret managers rather than hardcoding them in HCL.
- Always store remote state in a backend with encryption at rest and strict access controls.
- Never commit plaintext secrets in .tfvars files to version control.
- Enable audit logging on the state backend so secret exposure via state reads is traceable.
- Rotate secrets in the secret store independently of Terraform code changes to reduce blast radius.
Practice what you learned
1. What does marking a Terraform variable as sensitive = true actually do?
2. Why is committing a .tfvars file containing plaintext secrets to a private Git repository still risky?
3. What is the main advantage of fetching a secret via a data source from a secret manager instead of hardcoding it?
4. Which of the following is the best description of the relationship between Terraform state and secrets?
5. Which practice provides defense in depth for secrets that inevitably end up in Terraform state?
Was this page helpful?
You May Also Like
The Terraform State File
Terraform's state file is the source of truth mapping your configuration to real-world resources. Understanding its structure and risks is essential for safe, collaborative infrastructure management.
Remote State Backends
Remote backends store Terraform's state file outside the local disk, enabling team collaboration, durability, and integration with locking mechanisms.
State Locking
State locking prevents two Terraform operations from writing to the same state file simultaneously, protecting against corruption and lost updates.
Common Terraform Pitfalls
A field guide to the mistakes Terraform users make most often — from state file mishandling to accidental destroys — and how to avoid each one.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsAdvanced Bash Scripting Study Notes
Bash · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics