Terraform Error: 'Instance cannot be destroyed' Due to lifecycle prevent_destroy
Fix Terraform's 'Instance cannot be destroyed' error from lifecycle prevent_destroy: understand the guard, safely remove or override it, and handle destroy/replace and terraform destroy.
- #terraform
- #iac
- #troubleshooting
- #errors
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Exact Error Message
╷
│ Error: Instance cannot be destroyed
│
│ on database.tf line 1:
│ 1: resource "aws_db_instance" "primary" {
│
│ Resource aws_db_instance.primary has lifecycle.prevent_destroy set, but the
│ plan calls for this resource to be destroyed. To avoid this error and continue
│ with the plan, either disable lifecycle.prevent_destroy or reduce the scope of
│ the plan using the -target option.
╵
What It Means
The lifecycle { prevent_destroy = true } meta-argument is a safety guard. When it is set on a resource, Terraform refuses to produce any plan that would destroy that object — whether you ran terraform destroy, or a normal apply where a change forces replacement (destroy-then-create).
This error is Terraform working as intended: it caught a plan that would delete a resource you explicitly protected. It is common on stateful, hard-to-recreate infrastructure — databases, S3 buckets holding data, KMS keys — where an accidental delete would be catastrophic. Resolving it means deciding whether the destroy is genuinely intended, and if so, deliberately lifting the guard.
Common Causes
- You ran
terraform destroy(orterraform apply -destroy) against a stack containing a protected resource. - A configuration change forces replacement of the resource (a change to an immutable, ForceNew attribute triggers destroy-then-create).
- A
terraform state rmfollowed by re-import went wrong and the plan now wants to delete. - A module was removed or renamed without a
movedblock, so Terraform plans to destroy the old address. - The guard was left on from a previous project phase and now blocks an intended teardown.
Diagnostic Commands
See exactly what the plan wants to do and why the destroy/replacement is proposed:
terraform plan
Find every resource that carries the guard:
grep -R "prevent_destroy" --include="*.tf" .
Inspect the attribute change forcing a replacement (look for # forces replacement):
terraform plan -no-color | grep -B2 "forces replacement"
Confirm the resource’s current state entry:
terraform state show aws_db_instance.primary
Step-by-Step Resolution
First decide: do you actually want this resource destroyed? If not, the guard did its job — change your configuration so the plan no longer proposes a destroy (for example, revert the ForceNew attribute change).
If the destroy is genuinely intended:
- Locate the
lifecycleblock on the resource:
resource "aws_db_instance" "primary" {
# ...
lifecycle {
prevent_destroy = true
}
}
- Set the guard to
false(or remove the block) as a deliberate, reviewable change:
lifecycle {
prevent_destroy = false
}
- Re-plan to confirm the destroy is now allowed and is the only change you expect:
terraform plan
- Apply or destroy as intended:
terraform apply
# or, to tear the whole stack down
terraform destroy
- If the destroy is caused by an unintended replacement, do not remove the guard — fix the trigger instead. Revert the immutable attribute, or if the change is intentional and safe, plan it explicitly. To carve just the intended change out of a large plan, scope it:
terraform plan -target=aws_security_group.web
- If the resource was removed from config by accident, restore it or add a
moved/removedblock so Terraform stops planning a destroy. To drop it from management without deleting the real object:
removed {
from = aws_db_instance.primary
lifecycle {
destroy = false
}
}
Note that prevent_destroy must be a literal boolean — it cannot reference a variable — so toggling it is always a code change. If you manage many protected resources and want a consistent, reviewable pattern for lifting guards safely, the Terraform lifecycle prompts in the prompt library can generate the change set and a checklist.
Prevention
- Keep
prevent_destroy = trueon stateful, data-bearing resources (databases, buckets, KMS keys) by default. - Treat any change that lifts the guard as a high-scrutiny pull request requiring review.
- Watch plans for
# forces replacementso you catch unintended destroy-then-create before it hits the guard. - Use
moved/removedblocks when refactoring so renamed resources are not planned for destruction. - Take a backup/snapshot immediately before intentionally destroying a protected resource.
- Do not templatize
prevent_destroy; it must be a literal, so keep it explicit and per-resource.
Related Errors
... has lifecycle.create_before_destroy setinteractions — replacement ordering issues, related to lifecycle.Error: Instance cannot be destroyedon a module resource — same guard, addressed inside the module.Resource ... has been deleted outside of Terraform— the object is already gone, not a guard block.-target“Resource targeting is in effect” warning — expected when you scope a plan to sidestep an unrelated replacement.
Frequently Asked Questions
Can I override prevent_destroy on the command line? No — there is no flag to bypass it; you must edit the configuration to set it false (or remove the block) and re-plan, which keeps the override auditable.
Why does a normal apply hit this and not just terraform destroy? A change to an immutable attribute forces replacement, which is a destroy-then-create; the guard blocks the destroy half, so even a routine apply can trip it.
Can I set prevent_destroy from a variable? No — it must be a literal boolean known at plan time, so it cannot reference variables or expressions; toggling it is always a code change.
How do I stop managing a protected resource without deleting it? Add a removed block with lifecycle { destroy = false } (or use terraform state rm) so Terraform forgets the resource while leaving the real object intact. For more lifecycle fixes, see the Terraform guides.
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.