A Guide to Terraform Secrets Management

FinnLobsien1 pts0 comments

A Guide to Terraform Secrets Management: Keeping Secrets Secure in Infrastructure as CodeJoin us July 22:Secrets Management in the AI Era webinar. Save your seat.

27kRequest a demoGet started for free

27k starsRequest a demoGet started for free

← Back<br>Blog post • 25 min read<br>A Guide to Terraform Secrets Management: Keeping Secrets Secure in Infrastructure as Code

Published onFriday, July 10, 2026

Terraform is an excellent tool. Instead of clicking through consoles and running one-off setup scripts, you describe your whole infrastructure in code: three app servers, a Postgres database, a bucket for uploads. Terraform reads that, checks what already exists, and creates, updates, or deletes whatever it takes to make reality match.

To do that, it needs access to your infrastructure. So you hand it credentials: a cloud key to create the servers and the bucket, and an admin password to set up that Postgres database.

And then it puts those secrets in plaintext on disk, in a Terraform state file.

That's a problem twice over. State files travel: they get copied to laptops, shared through remote backends, and stored as CI artifacts, and every copy carries the secret. And when a secret has to change, every file holding a copy has to change with it.

Keeping secrets safe with Terraform means solving one specific problem: giving it the access it needs without any secret being stored in plaintext along the way. The way to do that is to keep the real values in a dedicated secrets management platform, hand Terraform references instead of payloads, and lean on the newer mechanisms that let a value pass through a run without ever being written to state.

There's setup involved, but it pays for itself: once the secrets manager is wired in, adding a secret is one write, rotating it is one change, and nothing needs to be scrubbed out of Git or state afterwards.

What is the problem with Terraform secrets?

Any secret Terraform handles, whether it's a credential you pass in or a value Terraform generates, gets written into its state file in plaintext. Anyone who can read that file can read the secret.

Let’s see this happen. Here's a configuration that doesn't touch a cloud provider, a database, or any real secret at all. It just generates a random password and marks the output sensitive:

terraform {<br>required_version = ">= 1.10"<br>required_providers {<br>random = {<br>source = "hashicorp/random"<br>version = "~> 3.6"

resource "random_password" "db" {<br>length = 20<br>special = true

output "db_password" {<br>value = random_password.db.result<br>sensitive = true

Apply it, and Terraform does exactly what you'd hope. The value is redacted everywhere it would normally print:

$ terraform apply -auto-approve<br>random_password.db: Creating...<br>random_password.db: Creation complete after 0s [id=none]

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

Outputs:

db_password =

So far, so safe. in the plan, in the outputs. But if we grep the file Terraform just wrote to the current directory, we’ll see something worrying:

$ grep -n '"result":' terraform.tfstate<br>35: "result": "YBG$nq2(UQjIBVSyekYB",

There it is. The password Terraform was so careful not to print is sitting in terraform.tfstate in plaintext. And not just once:

$ grep -n -A1 '"db_password"' terraform.tfstate<br>7: "db_password": {<br>8- "value": "YBG$nq2(UQjIBVSyekYB",

The resource attribute is stored, the output is stored, and the state carries a bcrypt hash of the same password for good measure. The sensitive = true flag controlled how the value was displayed, but it did nothing about how it was stored.

This is the whole problem in miniature. State is plaintext JSON by default. Every attribute of every resource goes into it, and Terraform needs it that way: state is how it knows what already exists so that it can compute the next diff. The leak is a side effect of Terraform working correctly.

How do secrets end up in state files?

There are a few ways a secret gets into state:

Values you pass in . A password argument on a resource, or a provider access_key.

Values Terraform generates . The random_password above is the clearest case: Terraform created the secret, so of course it stores it.

Values you read . Data sources fetch information and record it in state so future plans are stable. If that information is a secret, it's now in state too.

Outputs . Outputs are stored in state as well, which is why an output derived from a secret is itself a secret at rest.

Notice that sensitive = true doesn't appear on that list, because it doesn't change any of it.

Is this all a big deal? Yes. State files get around. They land in a shared remote backend, get copied to a laptop for a quick terraform state command, show up as a CI artifact, or get committed by someone who didn't realize what was in them. Every one of those is a copy of your secret, which is how secret sprawl starts.

So, what do you need to do? Keep real secret values out of state. And that means both kinds of secret...

terraform state secret secrets stored value

Related Articles