Chapter 5 of 24

State Without Fear

State is a protected mapping from configuration addresses to real objects, not a cache to delete when plans surprise you.

Learning objectives

State connects intent to identity

Sensitive data

State can contain IDs, attributes, and secrets. Encrypt it, restrict access, and do not commit local state or backups.

Address

aws_subnet.app["az-a"] is the configuration identity used to find one remote object.

Locking and backups

Remote backends serialize writes. Local state creates backup files; retain backend versions and recovery access.

Refactors

moved changes an address in state. removed intentionally stops managing an object without deleting it.

terraform state list
terraform state show 'aws_subnet.app["az-a"]'
terraform import aws_vpc.existing vpc-123
tofu plan -refresh-only

Use an import block when the import should be reviewed in configuration; use the command for an explicit one-time operational action. A removed block is not a delete request. A moved block belongs with the refactor so every environment can make the same address transition.

Lock rule: force-unlock only after proving the original CLI or CI job is no longer running and the lock ID belongs to this workspace.

AWS context

Production state should use an encrypted remote backend with versioned backups and a lock mechanism. Never paste a state file into a ticket or chat; it can expose values that plan output redacts.

Lab: rename without recreate $0

Open labs/05-state/main.tf and refactor.tf. The committed end state uses terraform_data.current_name; the moved block maps the old address.

Reproducible no-recreate proof with OpenTofu

(
set -e
workdir=$(mktemp -d)
trap 'rm -rf "$workdir"' EXIT
cp learn-terraform/labs/05-state/main.tf learn-terraform/labs/05-state/refactor.tf "$workdir"
cd "$workdir"
mv refactor.tf refactor.tf.disabled
perl -0pi -e 's/current_name/legacy_name/g' main.tf
tofu init -backend=false && tofu apply -auto-approve
perl -0pi -e 's/legacy_name/current_name/g' main.tf
mv refactor.tf.disabled refactor.tf
tofu plan -out=rename.tfplan
tofu show rename.tfplan
tofu apply rename.tfplan
tofu state list
tofu destroy -auto-approve
)

The saved final plan must say the legacy address has moved to terraform_data.current_name and end with 0 to add, 0 to change, 0 to destroy. Review and apply that exact plan, then confirm terraform_data.current_name in state before cleanup. Any destroy/create pair means stop: check both addresses and the moved block before apply.

Terraform

cd learn-terraform/labs/05-state
terraform init -backend=false
terraform state list
terraform plan

OpenTofu

cd learn-terraform/labs/05-state
tofu init -backend=false
tofu state list
tofu plan

Review a state transition

ExpectedMeaning
has moved toThe state address changes while the same remote object remains managed.
0 to add, 0 to change, 0 to destroyThe rename caused no infrastructure action.
-/+ or +/-Not a successful address-only refactor; investigate before apply.
refresh-only differenceObserved remote drift needs a deliberate follow-up decision.

Failure drill: partial apply recovery

After a failed or interrupted apply, run terraform plan or tofu plan, inspect state list and state show, and compare with the remote API. Import an object only when its configuration now declares the exact intended address. If an object must stop being managed but stay alive, review a removed block. Do not edit JSON state by hand unless following an established recovery procedure with a backup.

Never delete state to fix a plan: it severs identity mapping and can make the next apply attempt to recreate real infrastructure.

Verify

  1. Run the isolated rename proof and read the final action count.
  2. Confirm the final plan includes the moved-address message and no create/destroy pair.
  3. Run tofu state list after apply to see terraform_data.current_name.
  4. Destroy the temporary lab state after the proof.

Cleanup

In the temporary directory, run tofu destroy -auto-approve, then remove that temporary directory. Do not delete a real project’s state file, lock, or backend object as cleanup.

Quick check

Does a moved block create a second object?

No. It maps an existing state address to a new configuration address.

When is force-unlock valid?

Only after confirming the original owner has stopped and the lock belongs to this workspace.

What does a removed block do?

It intentionally stops managing an object without requesting its deletion.

Recap and next

State is operational source of truth for object identity. Protect it, inspect it deliberately, and preserve identity with moved blocks during refactors.

Next: Chapter 6 will introduce variables, contracts, and modules.