| Reason | The first-principles version |
|---|---|
| Mapping | Terraform must know which real object each config block owns. HashiCorp tried using cloud tags in early prototypes — failed, because not all resources/providers support tags. So the mapping is explicit, in state. |
| Dependencies | When you delete a resource from config, its dependency info is gone from config too — state remembers it, so Terraform still knows the correct destroy order. |
| Performance | State caches attributes. Querying every real resource on every plan is too slow at scale (API latency, rate limits) — hence -refresh=false exists. |
| Syncing | Teams need one agreed memory. Remote state + locking ensures runs start from the latest state and can't stomp each other. |
Source: Purpose of Terraform State
plan/apply after drift proposes to undo the manual change,
pushing reality back to match config. Refresh never changes your config, and drift never becomes
"accepted" on its own.
To accept drift instead of reverting it: update state with a reviewed
terraform apply -refresh-only, and update your config to match (if the drifted
attribute is set in config). One without the other and the next plan still wants to revert.
Delete the resource block → next apply destroys the real resource.
Or keep the block and run terraform destroy -target=….
terraform state rm ADDR — removes from state only. Real resource untouched, Terraform no longer knows it exists.
Modern (≥1.7): a removed block in config — declarative, reviewable forget.
state rm, the block is still in config → next plan says
+ create. For globally-named things (S3 buckets) the apply then fails — name taken.
The inverse of forgetting is terraform import (or an import block): adopt an
existing real resource into state.
| Command | Touches | Use when |
|---|---|---|
terraform state list | reads state | What does Terraform think it manages? |
terraform state show ADDR | reads state | Inspect one resource's recorded attributes. |
terraform plan -refresh-only | nothing (preview) | Detect drift: shows what refresh would change in state. |
terraform apply -refresh-only | state only | Accept reality into state — reviewed, no infra changes. Replaces deprecated terraform refresh. |
terraform plan -refresh=false | nothing | Skip the slow refresh; diff config vs last-known state. Fast, but blind to drift. |
terraform state rm ADDR | state only | Forget a resource (keep it alive, unmanaged). |
terraform state mv A B | state only | Rename/move without destroy+create (modern alt: moved block). |
terraform import ADDR ID | state only | Adopt an existing real resource (modern alt: import block + plan). |
Sources: refresh-only tutorial · drift tutorial · state command docs
terraform.tfstateJSON. For each resource: its address (aws_s3_bucket.logs), the real-world ID
(the mapping), all known attributes (the cache), and dependencies (the destroy-order memory).
Plus outputs and the state serial (version counter used for sync). It can contain
secrets in plaintext — treat state like a credential, never commit it.