linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Lessons
  • How it works
  • Knowledge base
  • Cheat sheet
  • Capstone
  • Interview prep
home/terraform/kb/Refactoring/tf-removed-block

kb/refactoring ── Refactoring ── intermediate

removed block: drop a resource from state, keep it in the cloud

`removed { from = ..., lifecycle { destroy = false } }` tells Terraform declaratively: remove this resource from management, but do not touch it in the cloud. The block was introduced in TF 1.7 and replaces the manual `terraform state rm` command. With `destroy = true` it behaves like an ordinary resource deletion from HCL.

view as markdownaka: terraform-removed, terraform-removed-block

Why removed

Sometimes you need to stop managing a resource with Terraform without deleting it from the cloud. A few situations where this comes up:

  • You are handing a system off to another team. They have their own Terraform project and will capture the resource via import.
  • You want to switch the resource to ClickOps or another tool, and the cloud resource must stay alive.
  • You are splitting a monorepo into two states: half the resources migrate. Use removed in one state and import in the other.

Before TF 1.7 you handled this with terraform state rm run by hand. That leaves no trace in HCL, skips PR review, and is easy to forget about. The removed block is declarative, the same way moved is.

Syntax

hcl
removed {
  from = aws_s3_bucket.legacy_logs
  lifecycle {
    destroy = false
  }
}
  • from: the resource (or module) address. Same format as moved.from.
  • lifecycle.destroy: what to do with the cloud resource.
    • false: leave it in the cloud (the primary use case). The state releases the resource; it becomes unmanaged.
    • true: delete it from the cloud (the same behavior as removing a resource block from HCL). This option is rarely needed. Usually it is simpler to just delete the resource block.

The resource block is removed from HCL at the same time. The removed block is the instruction for how to remove it: with or without destroy.

Minimal example: giving up management

Before:

hcl
resource "aws_s3_bucket" "legacy_logs" {
  bucket = "linuxlab-old-logs"
}

You want to stop managing the bucket with Terraform, but the bucket must stay (it holds three years of data):

hcl
# deleted the resource block
removed {
  from = aws_s3_bucket.legacy_logs
  lifecycle {
    destroy = false
  }
}

terraform plan:

# aws_s3_bucket.legacy_logs will no longer be managed by Terraform,
# but will not be destroyed
      bucket = "linuxlab-old-logs"
      ...
Plan: 0 to add, 0 to change, 0 to destroy.

After terraform apply, the state no longer contains the resource, but the S3 bucket remains. Once apply completes you can delete the removed block from HCL; it has done its job.

Use cases

Moving a resource between states

This is the most common scenario. State A drops the resource; state B captures it via import.

Steps:

  1. In state A (current):

    hcl
    removed {
      from = aws_s3_bucket.data
      lifecycle { destroy = false }
    }

    Run apply. State A no longer contains data.

  2. In state B (new):

    hcl
    import {
      to = aws_s3_bucket.data
      id = "linuxlab-data"
    }
    resource "aws_s3_bucket" "data" {
      bucket = "linuxlab-data"
    }

    Run apply. State B now owns the resource.

Between steps 1 and 2 the resource is unmanaged: nothing controls it. That window is brief, but the order of steps matters. Always run removed first, then import.

Removing an entire module

When you drop a whole module block:

hcl
removed {
  from = module.legacy_app
  lifecycle {
    destroy = false   # all resources inside the module stay in the cloud
  }
}

One block releases all resources inside the module. Without it, Terraform would see a missing module and try to destroy everything that was in it.

With destroy = true

hcl
removed {
  from = aws_s3_bucket.temp
  lifecycle {
    destroy = true
  }
}

This is equivalent to deleting the resource block from HCL: the resource will be destroyed in the cloud. The option exists for symmetry with destroy = false. In practice, if you want a destroy, deleting the resource block directly is simpler. removed with destroy = true usually shows up in automated code generation or for declarative audit trails.

removed vs state rm

removed blockstate rm
Lives in HCL, visible in diffOnly in shell history
Plan shows the change in advanceWrites to state immediately
Applies the same way for the whole teamOne person ran it
TF 1.7+ onlyAny version
Requires removing the resource block at the same timeDoes not touch HCL
lifecycle.destroy = true gives a real cloud deletionRemoves from state only, does not delete in the cloud

If TF 1.7+ is available, removed is always the better choice. The CLI state rm command remains useful for emergency situations.

What to combine it with

  • import block (tf-state-import): the typical step 2 after a removed with destroy = false. Used for moving resources between states.
  • moved block (tf-moved-block): use moved to relocate a resource rather than drop it. The two blocks sometimes appear together when a refactor both relocates some resources and releases others.
  • State backup before apply: required. Run terraform state pull > backup.json. If something goes wrong, state push backup.json returns the resource to management.

Pitfalls

  • lifecycle.destroy = false does not mean "never destroy." It means only "do not destroy during the apply of this removed block." If you later import the resource back into that state, normal management rules apply again, including a destroy when the resource block is deleted from HCL.

  • Between the removed apply in state A and the import apply in state B, there is an unmanaged window. If someone makes changes via the console or another tool during that window, those changes will not be reflected in either state. Keep the window short.

  • Deleting a resource block without a removed block means destroy. TF 1.7+ surfaces this through the removed block pattern. If you delete a block in a hurry and run apply, the resource is gone. S3 versioning backups and planned PR reviews help prevent that.

  • removed { from = module.X } does not distinguish between "drop the whole module" and "drop the module but keep the resources." The behavior is determined by destroy = false|true. Read carefully when reviewing a PR that contains this.

  • State lock applies. The removed block still writes to state, acquires a lock, and holds it until apply finishes. With a remote backend and DynamoDB that works normally.

  • Not for address changes. To rename or relocate a resource, use moved, not removed followed by recreation.

§ команды

bash
terraform plan

With a removed block: shows 'will no longer be managed' or 'will be destroyed' depending on lifecycle.destroy.

bash
terraform apply

Applies the removed block. The resource leaves the state. What happens in the cloud depends on lifecycle.destroy.

bash
terraform state pull > backup-pre-removal.json

Back up the state before running removed, in case you need to restore the resource to management later.

bash
terraform state list | grep -v <REMOVED_RESOURCE>

Verify that removed worked: the resource no longer appears in the state list.

§ см. также

  • tf-state-manipulationstate mv, state rm, state pull/push: manual operations`terraform state mv` renames a resource address in state without destroy/recreate. `terraform state rm` removes a resource from state but not from the cloud. `terraform state pull/push` downloads or uploads state as a file. All four are sharp operations; do them with a backup and a clear reason. For declarative alternatives, see [[tf-moved-block]] and [[tf-removed-block]].
  • tf-moved-blockThe moved block: rename without destroy`moved { from = ..., to = ... }` in HCL declaratively tells Terraform: "this resource used to live at one address and now lives at another, the cloud object is the same." The plan shows a "move", not a "destroy + create". It arrived in TF 1.1. It replaces the manual `terraform state mv`, leaves a trace in git, repeats for everyone on the team, and shows up in the diff.
  • tf-refactor-patternsRefactoring patterns: count to for_each, split files, extract moduleLarge configs turn into spaghetti. The core refactoring patterns are: count to for_each (stable keys), splitting files by domain (network/compute/storage), extracting a repeated block into a module, merging small resources into a composite one, and removing dead imports. Each pattern is covered step by step, with a `plan` check at every step.
  • tf-state-importImport: bringing an existing resource under Terraform managementImport means: this resource already exists in the cloud; start managing it. The old way: `terraform import <address> <cloud_id>`, and you write the HCL yourself. The new way (TF 1.5+): an `import` block directly in HCL. Plan shows what will happen; apply commits it. Import does not write HCL for you. That is your job.
  • tf-resource-lifecyclelifecycle: controlling resource behaviorThe lifecycle block configures four behaviors: create_before_destroy (zero-downtime replacement), prevent_destroy (deletion guard), ignore_changes (ignore drift on specific attributes), replace_triggered_by (force replacement on an external signal).
Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies