lesson ── terraform-intermediate ── ~15 мин ── 6 шагов
State is a file of records that say "resource address in HCL maps to a
concrete object in the cloud". Sometimes the address has to change (a
rename, a move into a module) while the cloud resource stays put. The CLI
terraform state mv/rm does exactly that.
In this lesson you will create a bucket, rename it in the state, confirm the bucket was not recreated, then drop the resource from state without a destroy.
Since TF 1.1+ the declarative moved block is the better choice for most
cases, and it gets its own lesson next. For now we cover the CLI, because
sometimes it is the only thing that works.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
cd /home/student/tf-state-ops
cat > main.tf <<'EOF'
resource "random_id" "suffix" {byte_length = 4
}
resource "aws_s3_bucket" "logs" { bucket = "linuxlab-state-ops-${random_id.suffix.hex}"}
EOF
terraform init
terraform apply -auto-approve
terraform state list
It should print:
aws_s3_bucket.logs
random_id.suffix
✓ The bucket is created under the address aws_s3_bucket.logs.
terraform state pull > /tmp/state-backup.json
ls -la /tmp/state-backup.json
jq '.serial, .lineage' /tmp/state-backup.json
This is your safety net. If something goes wrong, terraform state push /tmp/state-backup.json puts everything back.
With an S3 backend plus versioning this duplicates the automatic protection, but the habit costs nothing.
✓ Backup done. Now: state mv.
We want to rename aws_s3_bucket.logs to aws_s3_bucket.log_storage.
terraform state mv aws_s3_bucket.logs aws_s3_bucket.log_storage
Output:
Move "aws_s3_bucket.logs" to "aws_s3_bucket.log_storage"
Successfully moved 1 object(s).
Now you must fix the HCL, otherwise the plan shows a destroy on
log_storage and a create on logs:
sed -i 's/"aws_s3_bucket" "logs"/"aws_s3_bucket" "log_storage"/' main.tf
Check:
grep -n "aws_s3_bucket" main.tf
terraform state list
terraform plan
State and HCL agree. Plan: No changes.
✓ The address is renamed, and the cloud bucket was not recreated.
We want to drop the resource from management, but not delete the bucket in the cloud.
First a dry-run:
terraform state rm -dry-run aws_s3_bucket.log_storage
It shows:
Would remove:
aws_s3_bucket.log_storage
No changes. This is your sanity check. Always dry-run before the real rm. See tf-state-manipulation.
✓ The dry-run worked. Now: the real rm.
terraform state rm aws_s3_bucket.log_storage
Output:
Removed aws_s3_bucket.log_storage
Successfully removed 1 resource instance(s).
Check:
terraform state list
aws_s3_bucket.log_storage is gone. In the cloud, take a look:
aws --endpoint-url=http://localstack:4566 s3 ls
The bucket is still there. State "forgot" it, but it really exists. That bucket is now ownerless as far as terraform is concerned.
If you run terraform apply now without editing the HCL, Terraform
sees "log_storage is in HCL, not in state" and tries to create it
in the cloud. It fails with "bucket already exists".
This is a normal state while preparing for import (the next lesson).
To fix it right now, remove the resource from the HCL:
# remove the aws_s3_bucket.log_storage resource block
cat > main.tf <<'EOF'
resource "random_id" "suffix" {byte_length = 4
}
EOF
✓ State let go of the resource, and the cloud bucket stayed. That is state rm.
OpenTofu keeps the CLI and state compatible with Terraform for the
commands in this step: migration usually goes through mv .terraform .terraform.bak; tofu init -upgrade. On a first switch, though, back
up the state and do a run on a feature branch, the differences
cluster in the newer features (variables in backend,
state encryption, OCI registry-backed modules). See
tf-opentofu-parity for the full matrix.
If you wanted things back the way they were:
terraform state push /tmp/state-backup.json
Terraform checks lineage and serial:
-force skips the checks, but you usually do not need it.
terraform state list
You see all the resources from the backup. This is your safety net.
In real work with an S3 backend the backup happens automatically
(versioning). Restore is done with aws s3 cp s3://.../tfstate?versionId=....
✓ Restore is possible. A backup before these operations is basic hygiene, not paranoia.
The main rule: declarative beats imperative.
| Case | What to use |
|---|---|
| Rename (one person, in the current sprint) | state mv is faster, but moved is better for the PR |
| Rename (in a team, in the repo) | the moved block, it repeats for everyone |
| Drop from state without destroy | the removed block (TF 1.7+): visible in the diff |
| Emergency "get this resource out of the way while I figure it out" | state rm |
| Merging state from different roots | state mv with -state-out |
| Fixing broken state from a dump | state push (the only way) |
Rule: in a production repo the CLI is a last resort. Try the
declarative blocks first. The CLI leaves the operation with no trace
in git, and six months later nobody remembers why bucket.logs
became bucket.log_storage.
terraform state mv <SRC> <DST> changes an address in state without
touching the cloud. terraform state rm <ADDR> drops a record from
state while the cloud resource stays. Before either one, run
terraform state pull > backup.json.
команды
terraform state listwhat is in state: your starting pointterraform state pull > backup.jsona backup before a dangerous operationterraform state mv SRC DSTrename an addressterraform state rm -dry-run ADDRwhat rm would do, with no changesterraform state push backup.jsonrestore state from a dumpконцепции