skip to content

What does `terraform state rm` do to the real cloud object, and when is dropping a resource from Terraform state the right move rather than a mistake?

level: seniorimportance: must knowfreq 55%

answer

  1. forget, do not destroy
  2. the object keeps running and keeps billing
  3. config left behind means a duplicate create
  4. pair with an import on the other side
  5. 1.7 made it a reviewable block

basics

~20 s

terraform state rm deletes only the state entry: the cloud object keeps running, now managed by nobody. It is right when handing an object to another state or deliberately releasing it from Terraform; since Terraform 1.7 a removed block does the same thing declaratively.

solid answer

~1 min

`terraform state rm` makes Terraform forget an object. Nothing is destroyed — the instance, bucket or database keeps existing, but it is now unmanaged, invisible to plans, and paying for itself with no owner. That is the whole risk: it is the command people reach for to "make the plan stop complaining", and the result is an orphan nobody remembers. It is legitimate in two situations: you are handing the object to a different state or a different configuration, where you pair the removal with an import on the other side; or you are deliberately releasing a resource from Terraform management, in which case you also delete its resource block, or Terraform will simply propose creating a duplicate on the next plan. Since Terraform 1.7 the declarative form is a `removed` block with `from` and `lifecycle { destroy = false }`, which appears in the plan, gets reviewed, and runs in CI — set `destroy = true` if you actually want the object gone. And note the thing people get wrong: if the object was already deleted out of band, you do not need `state rm` at all, because a refresh notices the deletion and drops the entry for you.

code

hcl · 7 lines
hcl
removed {
  from = aws_db_instance.legacy

  lifecycle {
    destroy = false
  }
}

go deeper

for a junior

Know the headline: terraform state rm makes Terraform forget an object, it does not delete anything in the cloud, and the object carries on running and costing money.

for a middle

Explain the follow-on effects — a leftover resource block makes the next plan propose a create, which either conflicts on a unique name or silently duplicates the object — and know that a removed block is the declarative form.

for a senior

Demonstrate the operational discipline: record the object ID before removing, pair a removal with an import on the receiving side and in the safe order, and recognise that a refresh already handles objects deleted out of band.

for a principal

Own the estate consequence: orphans escape drift detection, policy scanning and environment teardown entirely, so the standard should be a reviewed removed block with a documented decommission owner, never an ad hoc command.

## What it does, precisely ```bash terraform state rm aws_db_instance.legacy ``` This removes the state entry binding that address to a real object. The provider is not asked to delete anything. After it runs: - The object still exists and still costs money. - Terraform no longer knows about it: it will not appear in plans, will not be refreshed, will not be destroyed by `terraform destroy`. - If the resource block is **still in the configuration**, the very next plan proposes *creating* it — because an address in config with no state entry means "not created yet". On resources with globally or regionally unique names, that create then fails with a name-already-exists error; on resources without that constraint, you get a silent duplicate. That last consequence is the one candidates most often miss, and it is why `state rm` is almost never a complete action on its own. ## The orphan An orphaned object is worse than a deleted one. It is not in any plan, so no drift detection covers it, no policy scan of your code sees it, and no destroy of the environment removes it. It surfaces months later as a line on a bill, or as a security group nobody can explain. Any deliberate `state rm` needs a follow-up: either the object is imported somewhere else immediately, or it is decommissioned by hand and the ticket says so. ## Legitimate uses **Handing a resource to another configuration.** You are splitting one root module into two, or moving a resource from the application state to the platform state. The object must not be recreated, so the sequence is: import it into the target state, confirm that plan is clean, then remove it from the source state. **Deliberately releasing management.** A resource is being taken over by another team or another tool. Remove it from state *and* delete its resource block in the same change. **A state entry whose object genuinely cannot be reconciled.** Rare, and usually a symptom of something else — a provider bug, or a half-completed apply. Worth exhausting other options first. A use that is usually **not** legitimate: the object was deleted in the console and now the plan errors or looks odd. Terraform handles that itself — refresh detects the object is gone and removes the entry, after which the plan simply proposes recreating it, which is what you want. ## The removed block Terraform 1.7 added the declarative equivalent: ```hcl removed { from = aws_db_instance.legacy lifecycle { destroy = false } } ``` You delete the `resource` block and add this in its place. `destroy = false` means forget the object and leave it running; `destroy = true` means actually destroy it. Because it is configuration, the plan shows what will happen before anyone approves, the change is reviewed as a diff, and CI performs it — rather than someone typing a destructive command with no record. It works for module calls too, so an entire module's worth of resources can be released in one declaration. The same lifecycle applies as with `moved` and `import` blocks: once applied everywhere, the block is inert and can be deleted. ## Moving a resource between two states The classic approach was `terraform state mv` with a separate output state, or a pull-edit-push dance. With modern Terraform the cleaner expression is a pair: an `import` block in the target configuration, and a `removed` block with `destroy = false` in the source. Both are reviewable, both show up in their plans, and the order matters — get the import in place and verified before the source stops tracking it, so no window exists where the object is unowned and someone runs a destroy. ## Safety Before any removal: `terraform state show` the address and record the object ID somewhere durable, because after the entry is gone that mapping exists only in your terminal scrollback. With local state, the mutating commands leave a backup file; with a remote backend you are relying on whatever versioning the backend provides, which is not something to discover during an incident. Removal takes the state lock like any other write, so it will not race with a concurrent apply — but it absolutely can race with a *pipeline* that applies just after your merge, which is one more argument for expressing it as a block the pipeline itself applies in order.

  • You run `terraform state rm` but leave the resource block in the configuration. What does the next plan show?
    A create. An address present in configuration with no state entry means "not yet created", so Terraform proposes building it. On a resource with a unique name the apply then fails with a conflict; without that constraint you quietly get a second object alongside the orphan. Removal from state and removal from configuration go together.
  • A colleague deleted an instance in the console and now wants to run `terraform state rm` to tidy up. Is that necessary?
    No. Refresh reconciles that automatically: Terraform reads the object, finds it gone, drops the entry, and the plan then proposes recreating it — which is usually exactly what you want. Reaching for `state rm` here risks removing the wrong address and hides a real drift event that should be discussed.
  • How do you move a resource from one state file to another without recreating it?
    Import it into the target configuration first and verify that plan is clean, then release it from the source with a `removed` block set to `destroy = false`. Doing it in that order means the object is never unowned. The older equivalent is `terraform state mv` with a separate output state, which works but leaves no trace in the repository.
  • What is the practical difference between a `removed` block with `destroy = true` and simply deleting the resource block?
    Almost none in outcome — both end with the object destroyed — but the `removed` block is explicit about intent rather than relying on the reader inferring it from an absence. The interesting value is `destroy = false`, which has no equivalent in simply deleting configuration: deleting always means destroy.

saying these in an interview costs you the question

  • Thinks terraform state rm deletes the cloud resource
  • Uses state rm to silence a plan they do not understand
  • Removes from state but leaves the resource block in the configuration
  • Removes from the source state before importing into the target
  • Believes an orphaned object will still be caught by drift detection

context