Skip to main content
RunBook Academy

TerraformX · State Operations: Read, Move, Remove, ImportProduction Terraform

state rm: Removing From State, Not From Reality

Intermediate⏱ ~12 minbash

What you'll learn

  • Use terraform state rm to detach a resource from state without destroying it
  • Distinguish state rm from terraform destroy (state vs reality)
  • Choose between state rm and import when an address needs to be re-adopted
  • Apply the backup-before-rm discipline and verify the plan afterwards

Prerequisites

None — start here.

Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-13

Not yet marked complete on this device.

There are two operations that remove a resource from production: terraform destroy (the real-world resource is deleted) and terraform state rm (only the state entry is deleted; the real-world resource keeps running). They are not interchangeable. state rm is the surgical tool for handing a resource over to a different management system, deleting a configuration block that no longer matches reality, or recovering from a wrong import. It is also one of the easiest commands to misuse; one mistyped address and a database is unmanaged.

What state rm does, and does not

terraform state rm <address> removes the state entry for one address. The real-world object is unaffected. The next plan sees the configuration but no state, and proposes to create a new real-world object — not destroy the existing one.

Before state rm:
   Configuration: aws_instance.web
   State:         aws_instance.web → i-0abc...
   Real world:    i-0abc... (running)

After state rm:
   Configuration: aws_instance.web
   State:         (empty)
   Real world:    i-0abc... (still running, now unmanaged)

Next plan:
   Configuration: aws_instance.web
   State:         (empty)
   Real world:    i-0abc... (still running)
   Plan:          create aws_instance.web (a NEW instance)

This is the critical behaviour: state rm does not destroy. The real-world resource keeps running until you destroy it explicitly or import it back under a different address.

When state rm is the right tool

  • Hand-off to a different system. A resource is being moved from Terraform management to a different system (Ansible, a manual runbook, another IaC tool). state rm detaches it cleanly.
  • Configuration no longer describes the resource. A refactor removed the resource block from configuration, but the real-world object should remain. Without state rm, the next apply would destroy it.
  • Wrong import cleanup. A resource was imported under the wrong address. state rm removes the wrong address; the correct address is then imported.
  • Recover from a half-failed apply. A resource was created in the real world but the state entry was lost. state rm cleans up the partial state and the real-world resource is re-imported under the correct address.

When state rm is the wrong tool

  • The resource should not exist anymore. Use terraform destroy (or remove the resource block and apply).
  • The address is wrong but the resource should still be managed. Use state mv or a moved block.
  • You want to “clean up” state because it is messy. state rm hides the mess; it does not fix it. Investigate first.

The procedure

# 1. Pull state to a backup file
terraform state pull > /tmp/state-before-rm.json

# 2. Confirm the address exists in state
terraform state list | grep 'aws_instance.web'
# aws_instance.web

# 3. Confirm the address exists in configuration (this is the safety check)
grep -A 1 '^resource "aws_instance" "web"' main.tf

# 4. If the resource block should also be removed, do that FIRST in a separate commit

# 5. Run the rm
terraform state rm -backup-file=/tmp/rm-backup.tfstate 'aws_instance.web'

# 6. Confirm the rm with state list
terraform state list | grep 'aws_instance.web'
# (empty)

# 7. Plan; expect the right next step
terraform plan
# If the configuration block was removed: no changes.
# If the configuration block remains: a new resource will be created.

The -backup-file flag is the recovery artifact. The pre-rm state is written to the file before the mutation, so a wrong rm can be undone.

Bulk operations

For an entire module:

terraform state rm -backup-file=/tmp/rm-backup.tfstate 'module.network'

This removes every resource in module.network from state. The real-world resources keep running. The next plan will propose to create them all again.

This is dangerous. The discipline:

  1. Confirm every resource in the module with state list 'module.network'.
  2. Confirm the team agrees with the hand-off.
  3. Pull state to a backup before running.
  4. Run the rm.
  5. Plan; expect the configuration to be either gone (no changes) or re-imported (a batch of creates).

Worked example: hand-off to Ansible

The team is migrating database management from Terraform to Ansible. The database is aws_db_instance.primary.

# Before migration:
terraform state show 'aws_db_instance.primary' | grep '^    id '
# primary-db.example.us-east-1.rds.amazonaws.com

# Step 1: remove from Terraform state (the database stays running)
terraform state rm -backup-file=/tmp/hand-off.tfstate 'aws_db_instance.primary'

# Step 2: remove the resource block from configuration
# (in a separate commit, after the rm)

# Step 3: import into Ansible inventory; verify Ansible can manage the database

# Step 4: terraform plan should now be empty for that resource
terraform plan
# No changes.

If the team changes its mind, restoring from the backup file puts the resource back under Terraform management.

Worked example: wrong import cleanup

An operator imported aws_s3_bucket.logs under the wrong address (module.logging.aws_s3_bucket.log_archive). The real-world bucket is the same one that should be managed at aws_s3_bucket.logs.

# Confirm the wrong address
terraform state list | grep 'aws_s3_bucket'
# module.logging.aws_s3_bucket.log_archive   (wrong)
# aws_s3_bucket.logs                          (not yet)

# Remove the wrong entry
terraform state rm -backup-file=/tmp/import-fix.tfstate 'module.logging.aws_s3_bucket.log_archive'

# Re-import under the correct address
terraform import 'aws_s3_bucket.logs' 'my-log-bucket-name'

# Verify
terraform plan
# No changes.

Validation

READ-ONLY

# Confirm the address is gone from state
terraform state list | grep 'aws_instance.web'
# (empty)

# Confirm the real-world resource is still running (this depends on the provider)
aws ec2 describe-instances --filters "Name=tag:Name,Values=web" \
  --query 'Reservations[].Instances[].State.Name'
# running

# Confirm the plan
terraform plan
# Either no changes (if config was also removed) or "create aws_instance.web" (if config remains)

The provider-side check is essential. The point of state rm is that the real-world resource keeps running. Verify it.

Production failure modes

Symptom: state rm succeeds but the next plan proposes to create the resource again. Cause: the resource block was not removed from configuration. Either remove the block (and re-plan) or re-import the real-world resource under the same address.

Symptom: state rm errors with “Resource not found in state”. Cause: the address is misspelled or in a different module path. Use state list to find the correct address.

Symptom: the wrong address was rm’d. Cause: address typo or wrong module path. Restore from the backup file; verify the correct addresses; retry.

Symptom: lock acquire failed during rm. Cause: another apply is in progress. Wait or break the lock with team agreement.

Symptom: serial advanced but the resource block in configuration was not removed, and the team now has duplicates. Cause: the rm was performed but the config was not updated. The duplicates exist now in the cloud. Decide: destroy the duplicate, or import it under a different address.

Recovery

  1. Stop. Do not apply.
  2. Pull the pre-rm state from the backup file: cat /tmp/rm-backup.tfstate to inspect.
  3. For S3 with versioning: restore the previous version of the state object. For Terraform Cloud: use the state versions UI.
  4. Verify with terraform plan — expect the original state.
  5. Investigate what went wrong with the rm before retrying.

What comes next

The next lesson covers terraform import — the inverse of state rm. Where state rm removes a resource from state, import adds it. The companion commands cover the lifecycle of adopting and handing off real-world resources.

Verification

  • You can describe the difference between state rm and terraform destroy.
  • You can name the production procedure for state rm: backup, confirm address, run, plan, verify.
  • You can choose between state rm, state mv, and import for each refactor scenario.
  • You can recover from a wrong state rm using the backup file.

Knowledge check · 7 questions

  1. Q1. What does `terraform state rm <address>` do?

  2. Q2. After `terraform state rm aws_instance.web`, the next `terraform apply` will destroy the real-world EC2 instance.

  3. Q3. Which flag writes a backup of the state before a state rm?

  4. Q4. When handing a real-world resource over to a different management system (e.g. Ansible), the right sequence is:

  5. Q5. Which of the following are valid use cases for `terraform state rm`? (Select all that apply.)

  6. Q6. After running `terraform state rm aws_instance.web` while the configuration block remains, the next plan will:

  7. Q7. An operator runs `terraform state rm aws_instance.wseb` (typo) instead of `aws_instance.web`. The real aws_instance.web is now unmanaged. What is the right recovery?

Passing score: 75%. Answers are checked in this browser.