Terraform Secrets: Best Practices for Storing and Managing Values

Chafik Belhaoues

How many of your Terraform configs right now contain a hardcoded credential that somebody meant to remove "after testing"? Be honest. It's probably more than one - and every single one is a breach waiting to happen. Terraform secrets management separates teams who ship confidently from teams who get paged at 3 AM.

Why Terraform Secrets Management Matters

IaC repositories are among the top sources of leaked credentials. One push to a public repo and bots pick up exposed keys within minutes. Deleting the commit doesn't help - Git preserves everything in its history.

State files compound the problem. Terraform stores attributes - including passwords and tokens - as plain text in terraform.tfstate. Loose permissions on the bucket containing that file expose your credentials.

SOC 2, PCI DSS, and HIPAA all require controls over how secrets get stored and audited. An AWS Secrets Manager Terraform integration without a clean audit trail is a gap auditors will find.

How to Use Terraform Secret Variables Safely

Terraform's built-in sensitive flag is the starting point. Mark a variable as sensitive=true, and Terraform hides its value from the CLI output and plan logs. Important: this prevents exposure in terminals and CI/CD logs, but the value still lands in the state file unencrypted.

For Terraform secret variables: declare sensitive inputs in variables.tf with the flag enabled, keep .tfvars files in .gitignore, and ideally skip .tfvars for secrets entirely - feed them through environment variables with the TF_VAR_ prefix or pull from an external secret manager at runtime.

The state file needs its own solution. A remote backend with encryption - S3 with server-side encryption, Terraform Cloud, or a self-hosted option with encryption at rest. Platforms like Brainboard separate variable values into the Terraform.tfvars file by design, allowing teams to apply different security strategies to that file versus the rest of the codebase.

Integrating AWS Secrets Manager with Terraform

If you're on AWS, this is the most natural path to keeping credentials out of code entirely.

The Terraform data secrets manager data source pulls secret values at apply time. Configure the AWS provider, declare aws_secretsmanager_secret_version pointing at your secret's ARN, and reference result_string wherever needed. The secret stays in AWS; Terraform fetches it at runtime; config files stay clean.

The win: a single source of truth that isn't a .tf file on someone's laptop. When credentials rotate, update Secrets Manager, and the next Terraform apply picks up new values. No code changes needed.

Best Practices for AWS Secrets Manager and Terraform

A few patterns that consistently prevent headaches:

  • Naming conventions matter. Use {env}/{service}/{secret-type} so secrets stay discoverable and don't turn into a junk drawer.
  • Automate rotation. AWS offers native rotation for RDS, Redshift, and DocumentDB. Static credentials are a ticking time bomb.
  • Lockdown is hard. The role running terraform apply gets read-only access to specific secrets - no wildcard policies.
  • Parse JSON explicitly. Always use jsondecode when retrieving JSON blobs. Raw JSON strings in resource attributes cause deployments to fail at 2 AM.

These patterns carry over to Terraform Cloud secrets, too, where workspace-level variable organization adds another layer worth getting right.

Managing Secrets in Terraform Cloud

Terraform Cloud changes the game for collaborative teams. Workspace variables marked as sensitive get encrypted at rest, masked in run logs, and made write-only through the API - set them, but can't read them back. That kills the "let me just check what this is set to" moments that end with secrets in Slack threads.

The state is encrypted by default. Runs execute in Terraform Cloud's environment, so secrets never touch a developer's machine. For teams that need centralized secret management without running Vault, this is the path of least resistance.

Alternative Tools and Approaches for Terraform Secrets

AWS Secrets Manager is one option. The right choice depends on your stack.

HashiCorp Vault is the heavyweight - dynamic secrets on demand, automatic expiration. But it needs dedicated infrastructure (3-5 nodes for HA). Worth it for large orgs; overkill for a team of five.

Azure Key Vault and Google Cloud Secret Manager cover similar ground. SOPS encrypts values inside config files using KMS, PGP, or age keys so you can commit encrypted files to Git.

What ties these approaches together is structure at scale. When you manage secrets in Terraform across multiple projects, proper scoping - at organization, project, or environment level - with enforced sensitive flags prevents each team from inventing its own approach.

Common Mistakes to Avoid with Terraform Secrets

The same ones keep showing up.

Committing .tfvars with real values - the number one leak vector. Add *.tfvars to .gitignore and maintain an example.tfvars with placeholders.

Ignoring state file encryption. terraform.tfstate contains every attribute Terraform manages, including Terraform secrets; the sensitive flag doesn't protect at the storage layer - remote backend with encryption at rest, period.

Overly broad IAM permissions. If the Terraform runner can access every secret in the account, a compromised pipeline means total compromise - scope to what each workspace needs.

Skipping rotation. Static credentials age badly. Automate rotation and verify Terraform picks up new values cleanly.

And one that's common: terraform output with -json or -raw on sensitive values during debugging, left in a CI script. Those flags bypass the sensitive marker - if pipeline logs are stored anywhere, those secrets are now in plaintext.

Building a Secure Foundation for Terraform Secrets Management

No single tool covers everything. The strongest setup layers controls: Terraform's sensitive flag for console exposure, Terraform environment variables with the TF_VAR_ prefix for CI/CD injection, a dedicated secrets manager as a single source of truth, and encrypted remote backends for state. Platforms like Brainboard add structural guardrails - variable scoping, enforced sensitive flag controls, and security scanning with Checkov and TFLint before anything deploys.

Start by auditing what you have today. Search repos for hardcoded credentials, check state file storage, and review IAM policies. The vulnerabilities are probably already there.

FAQ

What is the safest way to pass secrets to Terraform in a CI/CD pipeline?

Use your CI platform's native secret storage to inject values as environment variables with the TF_VAR_ prefix. Better yet, fetch secrets from AWS Secrets Manager, Terraform, or Vault at runtime. Never store secrets in unencrypted pipeline config files.

Why do secrets appear in Terraform state files?

State is a snapshot of real infrastructure. When Terraform creates a database with a password, it stores that password to detect changes. The sensitive flag hides values from logs but doesn't prevent them from being stored. Terraform 1.10+ introduced ephemeral resources that avoid persisting secrets in state.

How often should secrets used in Terraform be rotated?

90 days is a common baseline. Critical credentials should rotate every 30 days or automatically with Vault's dynamic secrets. If rotation requires manual steps, it won't happen consistently.

How do Terraform environment variables help protect secrets?

The TF_VAR_ prefix passes values at runtime without writing them to files. They exist only in the shell session or CI context. Combined with a secret manager, this keeps sensitive data out of code, version control, and state.

How does Terraform Cloud handle secrets differently from local workflows?

Sensitive variables are encrypted at rest, masked in logs, and write-only via API. The state is encrypted by default. Runs execute in Terraform Cloud's infrastructure, so secrets never reach developer machines.