Chapter 37: Terraform Import Moved and Removed Blocks
37.3 removed block — stop managing without deleting
Story. An Instagram-style team handed a CloudWatch log group to another team's Terraform. Simply deleting the block would make the next apply delete the log group and its history. A removed block with destroy = false tells Terraform to forget the resource but leave it running in AWS. It replaces risky manual terraform state rm in reviewed code.
removed {
from = aws_cloudwatch_log_group.app
lifecycle {
destroy = false
}
}
What each line does
removed {— Declares that this address should no longer be managed here.from = aws_cloudwatch_log_group.app— The address to forget; the resource block itself is deleted from code.lifecycle { destroy = false }— Keep the real log group in AWS; only remove it from state.
When it is green.
# aws_cloudwatch_log_group.app will no longer be managed by Terraform
Plan: 0 to add, 0 to change, 0 to destroy.
When it is red.
# aws_cloudwatch_log_group.app will be destroyed
# (because aws_cloudwatch_log_group.app is not in configuration)
Plan: 0 to add, 0 to change, 1 to destroy.
Fix. You deleted the resource block but forgot the removed block. Do not apply. Add the removed block with destroy = false and plan again.
What: every term in plain English
- removed block — Tells Terraform to stop managing an address.
- destroy = false — Keep the real object in AWS. Only forget it in state.
- destroy = true — Delete the real object too. Same as deleting the block, but clearer.
- hand-off — Giving a resource to another team or another state file.
- state rm — The old CLI way to forget a resource. No review.
- not in configuration — Plan text that means: the block is gone, so Terraform will destroy it.
- prevent_destroy — A lifecycle setting that blocks any plan that would delete the resource.
Example 1: Hand a log group to another team
An Instagram-style platform team gave the app log group to the app team. The logs must stay.
- Delete the aws_cloudwatch_log_group.app block from main.tf.
- Add the removed block.
- Plan and apply.
- Check the log group still exists.
removed {
from = aws_cloudwatch_log_group.app
lifecycle {
destroy = false
}
}
terraform plan
terraform apply
terraform state list
aws logs describe-log-groups --log-group-name-prefix /app/web --query "logGroups[].logGroupName"
Output
# aws_cloudwatch_log_group.app will no longer be managed by Terraform, but will not be destroyed
Plan: 0 to add, 0 to change, 0 to destroy.
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
[
"/app/web"
]
What it shows: state list no longer shows the log group, but AWS still has it.
Example 2: Move a bucket between two state files
The bucket must leave the platform state and join the data team state. removed in one, import in the other.
- In platform repo: delete the block, add removed with destroy = false, apply.
- In data repo: add an import block and the resource, apply.
# platform repo
removed {
from = aws_s3_bucket.events
lifecycle {
destroy = false
}
}
# data repo
import {
to = aws_s3_bucket.events
id = "demo-events-1234"
}
resource "aws_s3_bucket" "events" {
bucket = "demo-events-1234"
}
cd platform && terraform apply
cd ../data && terraform apply
Output
platform: Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
data: Apply complete! Resources: 1 imported, 0 added, 0 changed, 0 destroyed.
What it shows: Order matters: forget first, then import. Never let two states manage the same bucket.
Example 3: Forget a whole module
A legacy module created a VPC that the network team now owns.
- Delete the module call.
- Add removed for the module address.
- Plan.
removed {
from = module.legacy_vpc
lifecycle {
destroy = false
}
}
terraform plan
Output
# module.legacy_vpc.aws_vpc.this will no longer be managed by Terraform, but will not be destroyed
# module.legacy_vpc.aws_subnet.a will no longer be managed by Terraform, but will not be destroyed
Plan: 0 to add, 0 to change, 0 to destroy.
What it shows: All resources inside the module are forgotten together.
Example 4: Guard with prevent_destroy
A Paytm-style team wanted a hard stop so nobody deletes the payments bucket by mistake.
- Add prevent_destroy to the bucket.
- Delete the block by mistake and plan.
- See the error.
resource "aws_s3_bucket" "payments" {
bucket = "demo-payments-1234"
lifecycle {
prevent_destroy = true
}
}
terraform plan -destroy
Output
Error: Instance cannot be destroyed
on main.tf line 1:
Resource aws_s3_bucket.payments has lifecycle.prevent_destroy set, but the plan calls for this resource to be destroyed.
What it shows: prevent_destroy stops deletion. To hand it off, use removed with destroy = false instead.
Command cheat sheet
| Command | What it does | When to use it |
|---|---|---|
removed { from = A lifecycle { destroy = false } } |
Forget, keep in AWS | Hand-off |
removed { from = A lifecycle { destroy = true } } |
Forget and delete | Clear intent to delete |
terraform state rm A |
Old CLI forget | Emergency only, after state pull backup |
terraform state pull |
Save a copy of state | Before any state change |
lifecycle { prevent_destroy = true } |
Block deletes | Critical data |
Verify: did it work?
-
Gone from state
terraform state listOutput
(address not listed) -
Still in AWS
aws s3api head-bucket --bucket demo-events-1234; echo $?Output
0
When it is red 1
# aws_cloudwatch_log_group.app will be destroyed
# (because aws_cloudwatch_log_group.app is not in configuration)
Plan: 0 to add, 0 to change, 1 to destroy.
Cause. You deleted the block without a removed block.
Fix, step by step
- Stop. Do not apply.
- Add removed with destroy = false.
- Plan again.
When it is red 2
Error: Removed resource block still exists
This statement declares that aws_s3_bucket.events was removed, but it is still declared in a resource block.
Cause. The resource block is still in code.
Fix, step by step
- Delete the resource block.
- Keep only the removed block.
When it is red 3
Error: creating S3 Bucket (demo-events-1234): BucketAlreadyOwnedByYou
Cause. The new state tried to create instead of import.
Fix, step by step
- Add an import block for the bucket.
- Plan again and expect 1 to import.
Cleanup
aws logs delete-log-group --log-group-name /app/web
aws s3 rb s3://demo-events-1234 --force
Practice tasks
Easy Forget one bucket
Why: Basic hand-off.
What to do: Remove a bucket from state with destroy = false.
- Delete block, add removed, apply.
Check: Bucket still listed in S3.
Medium Move between states
Why: Two teams.
What to do: Move a bucket from folder A state to folder B state.
- removed in A.
- import in B.
Check: B plan shows no changes.
Medium See the danger
Why: Learn the red plan.
What to do: Delete a block without removed and read the plan.
- Do not apply.
Check: You saw 1 to destroy.
Easy Add prevent_destroy
Why: Safety net.
What to do: Protect one bucket and run plan -destroy.
- Read the error.
Check: Plan fails with the lifecycle error.
Easy Say it in two lines
Why: If you can say it simply, you understand it.
What to do: Write two short lines about this lesson for a fresher.
- Write the problem it solves.
- Write the one command or file you use.
- Read it aloud.
Check: A fresher understands it without a question.
Easy Start a safe lab folder
Why: Practice needs a throwaway place.
What to do: Make a new folder with versions.tf and a random_pet resource, so no AWS cost.
- mkdir tf-lab and cd into it.
- Add the hashicorp/random provider and one random_pet.
- Run terraform init and terraform apply.
Check: Apply complete! with 1 added.
Medium Break it on purpose
Why: You must know the red output by face.
What to do: Cause one error from the red part of this lesson and fix it.
- Follow one red section.
- Copy the exact message to your notes.
- Write the fix in one line.
Check: You fixed it without searching the web.
Medium Run it twice
Why: A second run must be quiet.
What to do: Run the same plan two times with no edits.
- Run terraform plan.
- Run it again.
- Explain any change it still shows.
Check: The second plan says No changes, or you can explain each line.
Medium Write a runbook page
Why: At 2 a.m. nobody remembers flags.
What to do: Write a one-page runbook for this lesson.
- Title, when to use, exact commands, how to check, how to undo.
Check: A teammate can follow it alone.
Medium Run it from a pipeline
Why: Real teams run Terraform from CI, not from laptops.
What to do: Run the main command of this lesson inside a GitHub Actions or Jenkins job.
- Add a job with hashicorp/setup-terraform or a Terraform Docker image.
- Give it AWS access through an IAM role, never a key in the file.
- Run init, then the command from this lesson.
Check: The job log shows the same output you saw on your laptop.
Medium Check the cost
Why: Labs left running cost real money.
What to do: Find what this lesson would cost per day if you forgot to destroy it.
- Open the AWS Pricing Calculator.
- Add each resource from your plan.
- Write the daily cost in your notes.
Check: You know the daily cost and you ran terraform destroy.
Hard Draw the flow top to bottom
Why: A drawing shows the order of work.
What to do: Draw this lesson as boxes from top to bottom.
- Start at your code, go through plan and state, end at AWS.
- Mark where each command reads or writes.
Check: Each box matches a real step you ran.
Hard Explain it like an interview
Why: Interviewers want real stories.
What to do: Record a 60 second answer about this lesson.
- Say the problem, the command, and one error you hit.
Check: Your answer has a real command and a real error message.
Homework: try at home
Try at home
Pick a resource another team should own. Write the removed block in your repo and the import block in theirs, and test both with plan only.
Recap
- Deleting a block means destroy.
- removed with destroy = false forgets but keeps.
- For hand-offs: removed first, then import in the new state.
- removed replaces state rm in reviewed code.
- prevent_destroy blocks deletes of key data.
FAQ
Which version do I need?
Terraform 1.7 or later for removed blocks.
Can I delete the removed block later?
Yes, after it has been applied in every environment.
Is state rm still useful?
Only in emergencies. Take terraform state pull first.
Does removed work on modules?
Yes, use the module address.
What if both states manage one bucket?
They will fight. Each apply may undo the other. Always remove first, then import.
Interview questions
-
How do you stop managing a resource without deleting it?
A removed block with lifecycle destroy = false, then apply.
-
How do you move a resource between two state files?
removed with destroy = false in the old state, then an import block in the new one.
-
removed vs state rm?
removed is code, reviewed and repeatable. state rm is a one-off command on one machine.
-
What does not in configuration mean in a plan?
The block was deleted, so Terraform will destroy the object.
-
How do you protect a critical bucket?
lifecycle prevent_destroy = true, plus versioning and backups.
Quick quiz
Q1. What keeps the object in AWS?
destroy = false.
Q2. Minimum version for removed?
Terraform 1.7.
Q3. Order for a hand-off?
removed first, then import.
Q4. What blocks deletes?
prevent_destroy.
Q5. Old CLI way to forget?
terraform state rm.
Lab
- Create one S3 bucket by hand in the console with a unique name.
- Write an import block and run terraform plan -generate-config-out=generated.tf.
- Clean up generated.tf and apply; confirm 1 imported.
- Rename the resource and add a moved block; confirm 0 to destroy.
- Add a removed block with destroy = false, apply, then delete the bucket by hand in the console to avoid cost.
Practice task
Write the moved blocks needed to change aws_instance.worker[0] and [1] into for_each keys blue and green.
Ravindra Bagale's Tip
If a plan shows a destroy you did not expect after a refactor, the answer is almost always a missing moved block.
In short
- import blocks adopt existing resources through a reviewed plan.
- -generate-config-out drafts config for imports.
- moved renames and relocates without destroy.
- removed with destroy = false forgets a resource but keeps it in AWS.
Samajla ka?
Revision card
Interview card