Terraform — Part 6 — Terraform State Explained
State has been mentioned in every part of this series so far without a proper explanation. This part opens it up: what it actually contains, why Terraform can’t work without it, and why hand-editing it is almost always the wrong move.
plan has nothing to compare your configuration against.
The default local state file — plain JSON, not meant to be hand-edited.
State ties a resource block’s local name to the actual object’s ID on the real platform.
Every plan compares configuration against state, not against the real infrastructure directly.
Introduction
State has come up in every part of this series so far — Part 1 described it as Terraform’s record of what’s already built, Part 3 covered how plan reads it. None of that explained what’s actually inside it, or why Terraform needs a file for this at all.
Here’s the question worth sitting with: your cloud provider already knows what infrastructure exists. Why doesn’t Terraform just ask it directly every time, instead of keeping its own separate record?
What State Actually Is
Think of state as Terraform’s own inventory ledger — not the infrastructure itself, but a running record of exactly what Terraform believes it has already built, cross-referenced against real-world IDs.
By default, this ledger is a single JSON file named terraform.tfstate, created in your working directory the first time you run terraform apply. A backup of the previous version, terraform.tfstate.backup, is kept alongside it.
Why State Exists
State solves four problems that a stateless tool couldn’t solve on its own:
Ties a config block like aws_instance.web to the actual object’s real ID, e.g. i-0abc123def456789.
Tracks dependency relationships, so Terraform knows the right order to destroy resources in, not just create them.
Caches attribute values, avoiding a fresh API call to every provider on every single command.
The fourth is the one this series has already used without naming it: terraform plan reads state, not your real infrastructure directly, to work out what has changed and what needs to happen next. State is the baseline every plan is diffed against.
What’s Inside a State File
A trimmed, illustrative excerpt — real state files are considerably longer and this shouldn’t be treated as something to copy or hand-construct:
{
"version": 4,
"terraform_version": "1.9.0",
"serial": 3,
"resources": [
{
"mode": "managed",
"type": "random_pet",
"name": "server",
"provider": "provider[\"registry.terraform.io/hashicorp/random\"]",
"instances": [
{
"attributes": {
"id": "witty-mule",
"length": 2
}
}
]
}
]
}
version is the state file format version, currently 4. serial increments on every change and is part of how Terraform detects conflicting writes. Each entry in resources records the resource’s type and local name from your configuration, alongside an instances array holding the real attribute values Terraform read back after creating it — this is exactly how random_pet.server.id from Part 4 and Part 5 gets a real value to hand back when referenced elsewhere.
Inspecting State Safely
Terraform provides a dedicated set of subcommands for looking at and adjusting state without hand-editing the file:
| Command | What It Does |
|---|---|
terraform state list |
Lists every resource currently tracked in state. |
terraform state show <address> |
Prints the full recorded attributes for one specific resource. |
terraform state mv |
Renames or relocates a resource’s entry in state without destroying and recreating the real object. |
terraform state rm |
Removes a resource from state tracking without deleting the real object. |
terraform state pull |
Prints the current raw state contents, useful for scripting or inspection. |
# See every resource Terraform is currently tracking
terraform state list
# Inspect one resource's full recorded attributes
terraform state show random_pet.server
Why You Should Never Hand-Edit It
Editing terraform.tfstate directly is technically possible and almost always the wrong move. Because plan trusts state as the baseline, a manually broken or inconsistent entry doesn’t just fail loudly — it can make Terraform propose the wrong plan entirely, including destroying and recreating resources that were actually fine.
terraform state mv or terraform state rm, not a text editor. Those commands understand the file’s structure; you’d be guessing at it.
Sensitive Data in State
State stores every attribute of every resource it tracks, in plain text, including values you marked sensitive = true back in Part 5. That flag only redacts CLI output — the state file itself has no equivalent protection built in.
This means an initial database password, a generated API key, or any other secret that ends up as a resource attribute is sitting in plain text inside terraform.tfstate for anyone who can read that file. The standard precautions: never commit state to version control, store it in a backend with encryption at rest enabled, and restrict who can read it with the same seriousness as any other credential store.
Local State’s Limits
Every example so far in this series has used the default local state file, sitting on disk right next to the configuration. That’s fine for learning and for genuinely single-person, single-machine work. It breaks down the moment more than one person, or more than one machine, needs to run apply against the same infrastructure.
A local file has no locking — two people running apply at the same time can corrupt it or silently overwrite each other’s changes. It also lives on one person’s laptop, which means losing that laptop means losing the record of everything Terraform has built.
Quick Reference Summary
| Term | Meaning |
|---|---|
terraform.tfstate |
The default local state file, plain JSON. |
serial |
Counter that increments on every state change, used for conflict detection. |
terraform state list |
Lists every resource currently tracked. |
terraform state show |
Prints one resource’s full recorded attributes. |
terraform state mv / rm |
Safely relocates or untracks a resource without editing the file by hand. |
Final Thoughts
State is the piece of Terraform that makes the declarative model in Part 1 actually work in practice — without a recorded baseline, every plan would have nowhere to diff against. It’s also the piece with the sharpest edges: plain text, holds secrets, and genuinely dangerous to edit carelessly.
terraform state subcommands instead of a text editor whenever it needs to change.
Terraform — Part 7 — Remote State and State Locking. We move state off a single laptop and into a shared backend, and cover why locking is what actually makes team use of Terraform safe.