Cloud  /  Terraform

IaC Terraform 50 guides · updated 2026

Infrastructure as code done right — providers, state, reusable modules, and the workflow patterns that keep multi-cloud deployments sane in 2026.

Sensitive Variables in Terraform

Terraform’s sensitive attribute prevents secret values from appearing in plan output, apply output, and the CLI. When a value is marked sensitive, Terraform displays (sensitive value) wherever that value would normally be shown — protecting secrets from leaking into logs, CI/CD dashboards, or terminal history.


Marking Variables as Sensitive

variable "database_password" {
description = "RDS master password"
type = string
sensitive = true
}
variable "api_key" {
description = "Third-party API key"
type = string
sensitive = true
}
variable "tls_private_key" {
description = "TLS private key PEM"
type = string
sensitive = true
}
variable "github_token" {
description = "GitHub Personal Access Token for CI"
type = string
sensitive = true
}

In plan output, sensitive variables appear as:

+ password = (sensitive value)
+ api_key = (sensitive value)

Sensitive Outputs

Any output that contains a sensitive value must itself be marked sensitive:

output "db_connection_string" {
description = "Database connection string"
value = "postgresql://${var.db_username}:${var.database_password}@${aws_db_instance.main.endpoint}/app"
sensitive = true
}
output "redis_auth_token" {
value = aws_elasticache_replication_group.main.auth_token
sensitive = true
}

If you reference a sensitive variable in an output without marking the output sensitive, Terraform will fail:

Error: Output refers to sensitive values
on outputs.tf line 5, in output "connection_string":
5: value = "...${var.database_password}..."

Sensitive Locals

When you derive a value from a sensitive variable, the derived local is automatically tainted as sensitive. You can also explicitly mark locals:

locals {
db_url = "postgresql://${var.db_user}:${var.database_password}@${aws_db_instance.main.endpoint}/prod"
# db_url is automatically sensitive because it contains var.database_password
# Explicitly mark a computed value
encoded_credentials = sensitive(base64encode("${var.username}:${var.password}"))
}

The sensitive() function (Terraform 0.15+) marks any expression as sensitive:

locals {
# Mark a computed string sensitive even if no input is sensitive
jwt_header = sensitive(base64encode(jsonencode({
alg = "HS256"
typ = "JWT"
})))
}

Accessing Sensitive Outputs

Sensitive output values are redacted in the CLI but accessible programmatically:

Terminal window
# Shows "(sensitive value)" in terminal
terraform output db_connection_string
# Returns the actual value in JSON format
terraform output -json db_connection_string
# Returns the raw string (for use in scripts)
terraform output -raw db_connection_string
# Store in variable without displaying
DB_URL=$(terraform output -raw db_connection_string)

Sensitive Values Are Still Stored in State

sensitive = true only controls display — it does NOT encrypt the value in state. The actual secret is stored in plaintext in terraform.tfstate.

{
"outputs": {
"db_connection_string": {
"value": "postgresql://admin:actualpassword@rds.endpoint.com/prod",
"type": "string",
"sensitive": true
}
}
}

Protect the state file with:

# Remote backend with encryption
terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "production/terraform.tfstate"
region = "us-east-1"
encrypt = true
kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/mrk-..."
dynamodb_table = "terraform-state-lock"
}
}

Common Anti-Patterns to Avoid

# BAD: Default value contains a secret
variable "db_password" {
type = string
sensitive = true
default = "hardcoded_password_123" # DON'T DO THIS
}
# BAD: Sensitive value in a non-sensitive output
output "connection_info" {
value = {
host = aws_db_instance.main.address
password = var.database_password # Terraform will error — requires sensitive = true
}
# Missing: sensitive = true
}
# BAD: Logging a sensitive value (it will appear in logs even if marked sensitive)
resource "null_resource" "debug" {
provisioner "local-exec" {
command = "echo ${var.database_password}" # EXPOSED in logs
}
}

Propagation: Sensitive Values Spread

If you pass a sensitive value into a resource attribute, that attribute is automatically treated as sensitive in diffs:

resource "aws_db_instance" "main" {
password = var.database_password # Automatically redacted in plan output
}
# Any reference to this resource's password attribute is also sensitive
output "db_password_again" {
value = aws_db_instance.main.password
sensitive = true # Required
}

Non-Sensitive Workaround for Debugging

In development, you may need to inspect a value that’s marked sensitive. Use nonsensitive() temporarily — never in production:

# DEVELOPMENT ONLY — remove before committing
output "debug_password" {
value = nonsensitive(var.database_password) # Displays in plaintext
}

Remove this before merging. Add a validation in CI:

Terminal window
# Check for nonsensitive() calls before merging
grep -r "nonsensitive(" . --include="*.tf" && echo "ERROR: nonsensitive() found — remove before merging" && exit 1