Terraform State Management¶
Why the state file is load-bearing, not just a cache¶
The state file is the only record connecting an HCL resource block
to the real-world resource it corresponds to — for
resource "aws_instance" "web", the state file is what actually
stores "this maps to EC2 instance i-0abc123def456." Without it,
Terraform has no way to know that the resource declared in HCL
already exists (leading it to try to create a duplicate) or how to
find it to check for drift or destroy it later. Losing the state file
doesn't just lose "bookkeeping" — it loses the entire link between
configuration and reality.
Local state is unsafe for anything beyond solo use¶
By default, state lives in a local terraform.tfstate file. Two
concrete problems the moment more than one person runs Terraform
against the same infrastructure:
No locking (two concurrent applies can corrupt the file or create duplicate resources) and no shared visibility (each engineer's local file can silently diverge from what's actually been applied by someone else).
Remote state with locking¶
terraform {
backend "s3" {
bucket = "my-tfstate-bucket"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
}
}
The classic AWS pattern: state stored in S3 (durable, versioned —
S3 versioning gives a rollback path if state is ever corrupted), with
a DynamoDB table used purely for locking — before apply (or
plan, depending on configuration) starts, Terraform writes a lock
record to DynamoDB; a second concurrent apply sees the existing lock
and blocks or errors instead of proceeding, closing the exact race
condition local state has no defense against. Terraform Cloud/Enterprise
provides the same locking + shared-state guarantee as a managed
service instead of self-hosted S3+DynamoDB.
Drift and terraform refresh / import¶
When real infrastructure changes outside Terraform (a manual console
edit, another automation tool, a resource deleted directly), the state
file becomes stale. terraform plan (which implicitly refreshes state
by re-reading actual resource attributes from the provider before
diffing) surfaces this as an unexpected diff — a change plan shows
that nobody actually made through Terraform.
terraform import handles the related-but-distinct case of a resource
that exists in the real world but was never created through
Terraform at all (created manually, or by a different tool) —
importing links an existing resource's ID to a resource block in HCL,
so Terraform starts tracking and managing it going forward, without
destroying and recreating it.
Common pitfall¶
Treating an unexpected plan diff as something to blindly apply
away is dangerous specifically because the diff doesn't distinguish
"someone made an unauthorized/accidental change that should be
reverted" from "someone made a deliberate emergency fix that the
Terraform config hasn't caught up to yet" — both look identical as a
diff. Investigating why a drift diff exists before applying it away
is the only way to avoid accidentally undoing a legitimate emergency
change (like a manual scale-up during an incident) the moment the next
routine apply runs.