State: The Heart of Terraform
State: The Heart of Terraform
Every time Terraform creates, updates, or destroys infrastructure, it records the outcome in a single authoritative file called state. Without state, Terraform would have no way to know what it already built, and every plan would try to create everything from scratch. State is therefore not a convenience feature — it is the mechanism that makes idempotent, incremental infrastructure management possible.
What State Actually Is
State is a JSON document (typically named terraform.tfstate) that maps every resource in your configuration to its real-world counterpart in the provider API. For an AWS EC2 instance, the state file holds the instance ID, AMI, private IP, security group associations, tags, and dozens of other computed attributes that Terraform did not know at plan time. These stored attributes are used to:
- Compute diffs — compare desired config against actual cloud state without making API calls for every attribute.
- Build the dependency graph — cross-resource references like
aws_subnet.main.idare resolved from state, not from live API calls. - Detect drift — when the real infrastructure is modified outside Terraform, state reveals the discrepancy.
- Enable targeted operations —
terraform apply -target=aws_instance.webworks because Terraform knows which resources exist.
The Three-Way Reconciliation
Every terraform plan performs a three-way diff:
- Desired state — your
.tffiles. - Recorded state —
terraform.tfstate. - Actual state — a fresh read from the provider API (a "refresh").
Terraform compares desired against recorded-plus-refreshed to produce the plan. This is why terraform plan makes real API calls: it needs to refresh the recorded state before diffing. You can skip the refresh with -refresh=false for speed, but only do that when you are certain nothing changed out-of-band.
Anatomy of the State File
The state file is not meant to be hand-edited, but reading it teaches you exactly what Terraform tracks. After running terraform apply on a simple VPC and subnet, the file contains a resources array where each entry carries:
mode—managed(created by Terraform) ordata(read-only data source).typeandname— e.g.,aws_vpc/main.provider— the provider address that owns this resource.instances— an array (one entry percountorfor_eachinstance) each holdingschema_versionand a fullattributesmap.
Inspecting State Safely
Never open terraform.tfstate in a text editor for routine inspection. Use the CLI commands that parse and display state cleanly, and that respect locking when remote state is in use.
The output of terraform state show aws_vpc.main looks like this when your VPC is live:
terraform state list as a health-check habit. In a large root module, seeing an unexpected resource in the list — or a missing one — is your first signal that something drifted or that a refactor broke an address. Run it after every apply in CI and pipe the output to a diff against the previous run.
State Operations You Must Know
Beyond inspection, the terraform state subcommand gives you surgical control:
terraform state mv— rename or move a resource address in state without destroying and recreating it. Essential when refactoring a flat config into modules.terraform state rm— remove a resource from state without touching the real infrastructure. Used when you want Terraform to "forget" a resource (e.g., you are handing it to another team's state).terraform import— bring an existing cloud resource under Terraform management by writing its attributes into state. Required for brownfield adoption.
state rm followed by an apply will destroy the real resource. A wrong state mv can cause a replace on the next plan. Always take a state backup first: terraform state pull > backup.tfstate. In production, never run state subcommands without a peer review and a rollback plan.
The State Serial and Version Guard
Each write to state increments a serial integer. If two engineers apply at the same time from different machines — both starting from serial 7 — the second one to finish will find that the remote state is already at serial 8 and refuse to overwrite it. This is how state acts as an optimistic concurrency lock when using a remote backend. Local terraform.tfstate has no such protection, which is exactly why local state is only safe for a single operator.
Sensitive Values in State
Terraform stores all resource attributes in state, including secrets like database passwords, private keys, and access tokens. Even if you mark an output as sensitive = true, the value is still present in plaintext inside the state file. This has direct production security implications:
- Never commit
terraform.tfstateorterraform.tfstate.backupto version control. Add both to.gitignoreimmediately. - Use a remote backend with encryption at rest (S3 + SSE-KMS, or Terraform Cloud) so state is never stored on a developer laptop.
- Restrict IAM / RBAC access to the state bucket to the CI/CD role and lead engineers only.