Skip to main content
Secrets can leak in many unexpected places: Terraform plan/apply output, CI logs, local files, and even your version control history. This guide walks through three complementary, pragmatic techniques to reduce exposure risk. Use them together across development and production environments. We’ll cover:
  • Sensitive variables — quick wins that hide values from CLI output and logs.
  • Environment variables — keep secrets out of your repository and .tfvars.
  • External secret stores — the recommended approach for production.
Start with the fastest improvements, then move toward more robust secret management.

Why this matters

Terraform often stores resource attributes (including secrets) in the state. Even when values are redacted from CLI output, the state may still contain plaintext secrets. Treat your state like any other sensitive datastore: use remote backends with encryption and strict access control.

Overview: techniques at a glance

TechniqueWhat it protectsTypical useLimitations
Sensitive variablesPrevents values from appearing in CLI output and logsDevelopment and quick wins in any projectDoes not prevent values from being saved to state
Environment variables (TF_VAR_<name> and provider creds)Keeps secrets out of .tf and .tfvars files and VCSLocal dev and CI with secret injectionSecrets read by Terraform may still be written to state
External secret stores (Vault, AWS Secrets Manager, Key Vault)Centralized secrets, rotation, ACLs, audit logsProduction and audited systemsIf Terraform reads secrets and writes plaintext into resources, state can still contain them; prefer secret references/ARNs

1) Sensitive variables

Marking variables and outputs as sensitive tells Terraform to redact those values from CLI output and logs. This is the fastest, lowest-effort change that reduces accidental exposure. Example variable definition (variables.tf):
variable "db_password" {
  description = "The database password"
  type        = string
  sensitive   = true
}
Using the variable in a resource (main.tf):
resource "aws_db_instance" "db" {
  identifier     = "my-db"
  instance_class = "db.t3.micro"
  engine         = "mysql"
  username       = "app_user"

  # Reference the sensitive variable normally
  password = var.db_password

  # ...other settings...
}
Output marked sensitive (outputs.tf):
output "db_password" {
  value     = var.db_password
  sensitive = true
}
Marking variables or outputs as sensitive hides them from CLI output and logs but does not remove values from the Terraform state. Always secure your state (use a remote backend with encryption and access controls).

2) Environment variables

Environment variables let you inject secrets without writing them into .tf or .tfvars, preventing accidental commits.
  • Use the TF_VAR_<name> convention to set input variables from the environment.
  • Provider credentials (for example AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, VAULT_TOKEN) are commonly passed via environment variables and are supported by most providers.
Example (Linux/macOS):
export TF_VAR_db_password="s3cr3t!"
terraform plan
terraform apply
Best practices for environment variables:
  • Prefer CI/CD secret stores or OS-level secret managers to inject variables for automated runs.
  • Avoid passing secrets via terraform apply -var 'db_password=...' if process-list exposure is a concern.
  • Use ephemeral shells and short-lived credentials where possible.
  • Remember: if Terraform writes the secret to a resource attribute, the value may still be captured in state.
Useful references: For production systems, use a dedicated secrets manager for centralized secret lifecycle management, auditing, and rotation. Options include HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, and Google Secret Manager. Two common patterns:
  • Read secret values at apply time and pass them into resources (convenient but may store plaintext in state).
  • Configure resources with secret references/IDs (preferred) so Terraform only stores identifiers (ARNs/IDs) instead of plaintext secrets.
Examples
  • AWS Secrets Manager (reading the secret at apply-time):
data "aws_secretsmanager_secret_version" "db" {
  secret_id = "my-db-password"
}

resource "aws_db_instance" "db" {
  identifier     = "my-db"
  instance_class = "db.t3.micro"
  engine         = "mysql"
  username       = "app_user"

  # Injects plaintext secret read at apply-time
  password = data.aws_secretsmanager_secret_version.db.secret_string
}
  • HashiCorp Vault (Vault provider data source):
provider "vault" {
  address = "https://vault.example.com"
  # Authenticate via environment variables like VAULT_TOKEN
}

data "vault_kv_secret_v2" "db" {
  mount = "secret"
  name  = "prod/db"
}

resource "aws_db_instance" "db" {
  identifier     = "my-db"
  instance_class = "db.t3.micro"
  engine         = "mysql"
  username       = "app_user"

  password = data.vault_kv_secret_v2.db.data["password"]
}
Important considerations when using secret stores:
  • If Terraform reads a secret and writes the plaintext into a resource attribute, the plaintext can end up in state. To avoid this, prefer APIs that accept secret references (for example, pass a Secrets Manager ARN or Key Vault secret reference).
  • Design infrastructure to use secret references/IDs where possible so Terraform stores only identifiers.
  • Enforce least privilege, short-lived credentials, rotation policies, and audit logging on your secret manager.
  • Secure any provider tokens used to read secrets (store them in CI secret stores or encrypted environment variables).
ActionWhy
Mark inputs/outputs as sensitiveAvoid accidental printing in CLI output and logs
Use TF_VAR_ or CI secret injectionKeep secrets out of .tf and .tfvars and out of VCS
Prefer secret references (ARNs/IDs) in resourcesPrevents plaintext secrets from being stored in state
Use external secret managers for productionCentralized control, rotation, auditing
Secure remote state backendEncrypt state at rest and enforce ACLs
Avoid passing secrets on the CLICommand-line arguments can be visible to other processes/users

Quick reference: do’s and don’ts

  • Do: mark inputs/outputs as sensitive to reduce accidental exposure in logs.
  • Do: inject secrets via environment variables or CI/CD secret stores rather than committing them.
  • Do: use external secret managers in production and prefer passing secret references (ARNs/IDs) to resources.
  • Do: secure your Terraform state with a remote backend, encryption, and strict access controls.
  • Don’t: hardcode secrets in .tf or .tfvars that are committed to version control.
  • Don’t: pass secrets on the terraform command line if process-list leakage is a concern.
  • Don’t: assume sensitive prevents secrets from being stored in state—treat state as sensitive.
Best practice summary:
  • Use sensitive for variables and outputs to avoid CLI leaks.
  • Prefer environment variable injection for development and CI.
  • Use a secrets manager for production and pass secret references (ARNs/IDs) when possible.
  • Always protect and monitor your Terraform state backend.

Closing

These three approaches are complementary. In real projects you’ll often:
  1. Mark variables and outputs sensitive to reduce accidental exposure.
  2. Use environment variables or CI secret injection for development and automated pipelines.
  3. Rely on a dedicated secret manager in production—prefer secret references so Terraform does not store plaintext secrets in state.
Always treat your Terraform state as a sensitive asset: encrypt it, restrict access, and audit read permissions. For deeper reading, consult the official Terraform docs on variables, state, and remote backends:

Watch Video