Sensitive Data in Terraform

Handle sensitive data with variables in Terraform — learn to mark variables as sensitive, protect secrets in state and logs, and apply best practices for secure infrastructure-as-code.

Focus: handle sensitive data with variables

Sponsored

You've committed your main.tf to Git, pushed it to your team's repo, and only later realized that your AWS access key is sitting there in plain text for anyone with read access to see. Or worse — you ran terraform plan in a CI log and the output showed your database password in full. If you're managing infrastructure as code, sensitive data will leak through variables, state files, and logs unless you explicitly protect it. This lesson shows you exactly how to handle sensitive data with variables in Terraform, so your secrets stay secret while your code stays shareable.

The Problem This Lesson Solves

Terraform treats variables as plain text unless you tell it otherwise. When you define a variable like db_password without any protection, Terraform will:

  • Print its value in terraform plan and terraform apply output when you reference it.
  • Store it in plain text in your terraform.tfstate file, which may be committed or synced to remote state backends.
  • Expose it in logs generated by CI/CD pipelines or your local terminal.

Even if you're careful about which files you commit, state files capture every resource attribute, including secrets. This is one of the most common ways credentials leak in real-world infrastructure pipelines. For example, a developer might use a variable for an API token, run terraform apply, and the next day the token appears in a screenshot shared in a Slack channel.

Pro tip: Never rely on "just don't look at the output." You need explicit mechanisms to protect sensitive data throughout the entire Terraform lifecycle — from definition to state to plan output.

Additionally, many beginners hardcode secrets directly in variables.tf defaults or in resource blocks, which is even worse because it's both visible and version-controlled. The solution is not to avoid variables, but to use them with sensitivity controls built in.

Core Concept / Mental Model

Think of Terraform variables as labeled boxes. By default, the contents are visible to anyone who opens the box (i.e., views the plan or state). The sensitive attribute is like a lock on the box — it doesn't hide the box, but it ensures the contents are never printed in logs or plan output.

But there's a second layer: even if the variable is locked in the plan, the state file is like a separate ledger that records everything, including secrets. To protect data in state, you must use encryption and access controls on the remote backend.

Here's a mental model in three layers:

  1. Variable definition — Type, description, and sensitive = true flag.
  2. Plan/apply output — The CLI, which respects the sensitive flag and redacts values.
  3. State file — The persistent record, which stores raw values unless you encrypt it.

A common misunderstanding is that marking a variable as sensitive alone is enough. In reality, you also need to secure the state backend and avoid writing secrets to files in plain text.

How It Works Step by Step

To handle sensitive data with variables, you follow this sequence:

  1. Define the variable with sensitive = true in your variables block.
  2. Pass the value via environment variables, CLI flags, or a secrets tool (not in the code).
  3. Reference the variable in resources as normal — the sensitive flag only affects CLI output, not usage.
  4. Secure the state backend — use an encrypted backend like S3 with server-side encryption (SSE) or a managed service like Terraform Cloud.
  5. Verify that your plan output redacts the value and that your state file doesn't leak it in logs.

Let's break down each step.

Step 1: Mark Variables as Sensitive

In variables.tf, add sensitive = true to any variable that holds secrets:

variable "db_password" {
  description = "Password for the application database"
  type        = string
  sensitive   = true
}

variable "api_key" {
  description = "API key for third-party service"
  type        = string
  sensitive   = true
}

This tells Terraform to redact the value in plan/apply output. In Terraform 0.14 and later, you'll see (sensitive value) instead of the actual string.

Step 2: Supply Values Securely

The best way to provide values is via environment variables, which Terraform automatically picks up if they follow the TF_VAR_ naming convention:

export TF_VAR_db_password='SuperSecret123!'
export TF_VAR_api_key='sk-abc123xyz'
terraform plan

You can also use a CLI flag: terraform plan -var 'db_password=SuperSecret123!' — but be aware this may show up in your shell history. For production, use a secrets manager like Vault, AWS Secrets Manager, or an external data source.

Step 3: Reference in Resources

You reference sensitive variables exactly like any other variable — the sensitive flag doesn't change how you use them:

resource "aws_db_instance" "app_db" {
  identifier     = "app-db"
  engine         = "postgres"
  username       = "admin"
  password       = var.db_password
  # ...
}

Step 4: Secure the State Backend

State files store everything in plain text. If you use a local backend, you must encrypt the file yourself and restrict access. For teams, use a remote backend with encryption and access controls. For example, with S3:

terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/network/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

Pro tip: Enable S3 bucket versioning and add a bucket policy that denies public access. If a secret leaks into state, you can roll back to previous versions — but you should also rotate the secret immediately.

Step 5: Verify Redaction

After marking variables as sensitive, run terraform plan. You should see (sensitive value) in the output. If you see the actual value, you likely forgot to set sensitive = true or you're using a data source that doesn't support it.

Hands-On Walkthrough

Let's put this into practice. Create a directory with three files: main.tf, variables.tf, and outputs.tf. We'll simulate a simple scenario with a dummy provider (like the random provider) to avoid real cloud costs.

1. Define Variables

variables.tf

variable "db_password" {
  description = "Password for the database"
  type        = string
  sensitive   = true
}

variable "api_key" {
  description = "API key for a service"
  type        = string
  sensitive   = true
}

2. Use the Variables

main.tf

resource "random_password" "generated" {
  length  = 16
  special = true
}

resource "local_file" "example" {
  content  = "db_password: ${var.db_password}\napi_key: ${var.api_key}"
  filename = "example.txt"
}

# This output is intentionally marked sensitive
output "db_password" {
  value     = var.db_password
  sensitive = true
}

outputs.tf

output "all_secrets" {
  value = {
    db  = var.db_password
    api = var.api_key
  }
  sensitive = true
}

3. Set Environment Variables and Run

export TF_VAR_db_password='P@ssw0rd!'
export TF_VAR_api_key='sk-secret'
terraform init
export TF_LOG=TRACE # optional, to see the redaction in action
export TF_LOG_PATH=trace.log
export TF_LOG=
terraform apply -auto-approve

Expected Output (excerpt):

# local_file.example will be created
+ resource "local_file" "example" {
    + content              = (sensitive value)
    + filename             = "example.txt"
}

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

db_password = (sensitive value)
all_secrets = (sensitive value)

Notice that the content and outputs show (sensitive value) instead of the actual secrets.

4. Check the State File

Now look at terraform.tfstate — you'll see the raw values in plain text. This is expected, but it's why you must secure the backend.

cat terraform.tfstate | grep -E 'content|db_password|api_key'

You'll see the actual values. This demonstrates the need for encryption at rest.

Compare Options / When to Choose What

When handling sensitive data, you have several approaches. Here's a comparison:

Approach Pros Cons Best For
Environment variables Simple, easy to use, no extra tooling Can be seen in process lists, not suitable for complex secret rotation Local development, small projects
Terraform Cloud/Enterprise variables Centralized, versioned, encrypted at rest Requires paid plan or enterprise setup Teams with shared workspaces
External secrets manager (Vault, AWS Secrets Manager) Full secret lifecycle, audit logs, rotation, least-privilege access Requires extra infrastructure and code (data sources) Production environments, compliance-driven orgs
random provider Generates secrets on the fly, no manual input Secrets are still in state, requires careful handling Non-production, transient resources

When to Choose What

  • Choose environment variables for local development and quick tests — you avoid committing secrets.
  • Choose a secrets manager for production — you get rotation, IAM, and centralized control. You can fetch secrets with a data source:
data "aws_secretsmanager_secret_version" "db" {
  secret_id = "prod-db-password"
}

resource "aws_db_instance" "app" {
  password = data.aws_secretsmanager_secret_version.db.secret_string
}
  • Choose Terraform Cloud variables if your team is already using it — it's the easiest way to share variable values without exposing them in repos.

Troubleshooting & Edge Cases

Mistake: Marking Variables Sensitive But State Still Leaks

Problem: You set sensitive = true, but the state file still contains the raw secret. Alternate fix: Remember that sensitive only affects CLI output. You must encrypt the state backend (e.g., S3 SSE, Terraform Cloud) to protect at rest. Also, rotate secrets if they may have been exposed.

Mistake: Outputs Show Sensitive Values Despite sensitive = true

Problem: An output declared as sensitive appears in plain text in the CLI. Alternate fix: In Terraform 0.12 and earlier, outputs didn't have a sensitive flag. Always use sensitive = true in output blocks. If you're using a module, ensure the module's outputs are also marked sensitive.

Mistake: Passing Variables on the Command Line

Problem: You use -var 'token=secret' and it shows up in shell history or CI logs. Alternate fix: Use environment variables (TF_VAR_token) or a secrets manager; avoid CLI flags for anything sensitive.

Mistake: Using the random Provider for Production Passwords

Problem: Generated passwords are stored in state and may be lost or exposed. Alternate fix: For production, use a secrets manager to generate or store credentials. The random provider is fine for non-production, but treat its output as sensitive and secure the state.

Edge Case: Sensitive Variables in for_each or String Interpolation

Problem: You reference a sensitive variable in a string that isn't marked sensitive, causing a warning or leakage. Alternate fix: Always use the sensitive true flag on outputs and be careful with join and format — if you combine a sensitive value with a non-sensitive string, the result may lose sensitivity. Use sensitive() in outputs where needed.

What You Learned & What's Next

You've learned how to handle sensitive data with variables in Terraform: you can mark variables as sensitive to redact them in plan/apply output, supply values via environment variables or secrets managers instead of hardcoding, protect the state backend with encryption, and verify redaction in output. You also explored alternatives and troubleshooting for common mistakes.

You can now: - Define sensitive variables with sensitive = true. - Supply values securely via TF_VAR_* or external secrets. - Secure the state backend to protect at-rest data. - Debug common leaks and misconfigurations.

What's next: In the next lesson, you'll learn about remote state locking and consistency — how to prevent concurrent modifications and keep your team's infrastructure in sync. You'll apply these secrets-handling techniques in a collaboration context.

Keep your secrets out of code and logs, and your infrastructure will be safe as you scale.

Practice recap

Create a new empty directory, define three variables (db_user, db_password, api_token) with sensitive = true, and use them in a local file resource. Run terraform plan with TF_VAR_ environment variables and confirm the output shows (sensitive value) for the file content and outputs. Then inspect the state file to see the raw values — this reinforces why you must secure the backend. Next, try using a data source to pull a secret from your cloud provider's secrets manager.

Common mistakes

  • Marking a variable as sensitive but still storing plaintext in state — sensitive only hides CLI output, not state.
  • Using -var 'secret=value' on the command line, which exposes the secret to shell history and process listings.
  • Hardcoding secrets as default values in variables.tf or inside resource blocks — always visible in the repo.
  • Forgetting to mark outputs as sensitive, so values reappear in console output even when the variable is protected.
  • Reliance on the random provider for production credentials without securing the state backend.

Variations

  1. Use a secrets manager like Vault or AWS Secrets Manager to fetch secrets via data sources instead of inputting them manually.
  2. Use Terraform Cloud or Enterprise variables to store secrets encrypted at rest and manage them per workspace.
  3. Use the terraform.tfvars file (with prevent access via permissions and outside VCS) instead of environment variables — but remember it still holds plaintext.

Real-world use cases

  • Automated CI/CD pipelines that provision AWS resources using API keys injected as TF_VAR_ env vars without leaking plan logs.
  • A team managing a multi-environment deployment where database passwords are stored in AWS Secrets Manager and fetched during apply.
  • An organization with audit requirements that protects state files in an encrypted S3 bucket and rotates secrets automatically.

Key takeaways

  • Mark any variable holding credentials, tokens, or passwords with sensitive = true in its variable block.
  • Never put secrets in code or VCS; supply values via environment variables, CLI flags, or secrets managers.
  • The sensitive flag only redacts CLI output — state files store plaintext, so always encrypt and restrict access to backend storage.
  • Thoroughly test with terraform plan to ensure you see (sensitive value) instead of the actual secret.
  • Use a secrets manager for production to enable rotation, auditing, and least-privilege access.
  • When combining sensitive values with non-sensitive ones in expressions, verify the output doesn't become non-sensitive unexpectedly.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.