What does `prevent_destroy = true` in a Terraform resource's lifecycle block actually do, and what does it fail to protect against?
answer
- a guardrail, not a permission system
- fails the plan rather than skipping
- replacement counts as a destroy
- literal true only, no variables
- delete the block and the guard goes too
basics
~20 sprevent_destroy makes Terraform reject any plan that would destroy that resource, with an error instead of a deletion. It is a guardrail in the configuration only: delete the resource block itself and the guard disappears with it.
solid answer
~50 sSetting `prevent_destroy = true` inside a resource's `lifecycle` block tells Terraform to fail the run — not silently skip the resource — whenever a plan would destroy that object. That includes `terraform destroy`, and it includes any change that forces replacement, because a replacement contains a destroy. It is the standard guard on databases and stateful stores. Its limits matter as much as its behaviour: the value must be a literal `true`, so it cannot be driven by a variable; it only constrains Terraform, so nothing stops someone deleting the object in the console or removing it from state; and — the classic gotcha — if you delete the whole resource block from the configuration, you delete the `prevent_destroy` setting along with it, and the next apply destroys the object without complaint. For real protection, pair it with the provider's own deletion protection and with IAM.
code
hcl · 14 linesresource "aws_db_instance" "primary" {
identifier = "app-primary"
engine = "postgres"
instance_class = "db.t3.medium"
allocated_storage = 100
username = "app"
password = var.db_password
skip_final_snapshot = false
deletion_protection = true
lifecycle {
prevent_destroy = true
}
}go deeper
Be ready to say it makes Terraform refuse, with an error, any plan that would destroy the resource, and that it is what you put on a production database.
Explain that the failure is plan-wide rather than a skip, that a forced replacement trips it because replacement includes a destroy, and that the value must be a literal boolean.
Show where the guard actually holds: it constrains Terraform only, disappears if the resource block is deleted, and must be layered with provider-side deletion protection, IAM denies and backups.
Own the policy question — which classes of resource must carry the guard, how that is enforced across many repositories, and what the reviewed procedure is for the rare change that legitimately needs it lifted.
## What it does `prevent_destroy` is one of the arguments of a resource's `lifecycle` block: ```hcl resource "aws_db_instance" "primary" { # ... lifecycle { prevent_destroy = true } } ``` When Terraform builds a plan and finds that this resource would be destroyed, it stops with an error naming the resource, and the whole plan fails. Nothing is applied — it is not a per-resource skip that lets the rest of the run proceed. The intent is to make an accidental deletion of something irreplaceable a loud, blocking failure at plan time, which is exactly when a reviewer is still looking. It fires in every situation that involves a destroy: - `terraform destroy` on a configuration containing the resource. - Any change to an attribute the provider cannot update in place, because that plan is a destroy plus a create. This surprises people: they meant to change something small, and the guard blocks them because the provider decided the change forces replacement. The value must be a literal boolean. Terraform processes it too early to evaluate expressions, so `prevent_destroy = var.is_prod` is rejected outright. ## What it does not do This is the more interesting half of the answer, and the half interviewers are usually probing. **It is not protection against removing the resource from the configuration.** The guard lives inside the resource block. Delete the block, and the setting is gone from the configuration that Terraform is comparing against state; the object is simply no longer desired, and the next apply destroys it. Renaming the resource address has the same effect unless the move is recorded, because the old address is gone and the new one is a create. This is the single most common way a "protected" database dies. **It is not protection against anything outside Terraform.** Deleting the object in the cloud console, through the CLI, or via another automation is unaffected — Terraform only learns about it on the next refresh, where it appears as an object that has vanished. Likewise, dropping the resource out of state means Terraform no longer manages it, and the guard no longer applies to it. **It is not a substitute for the provider's own protection.** Many resource types have a real, server-side deletion guard — `deletion_protection` on `aws_db_instance` is the obvious one — which is enforced by the cloud API against every caller, not just against Terraform, and which *can* be set from a variable. The layered answer in an interview is: `prevent_destroy` for the Terraform plan, provider-side deletion protection for the API, IAM denies for the humans, and backups for when all three fail. ## Working with it day to day Because the guard blocks legitimate work too, teams hit the moment where a genuine change forces replacement of a protected resource. The honest options are to change the configuration so replacement is not forced, to ignore the attribute that forces it, or to make a deliberate, reviewed edit removing the guard for that one change and putting it back. What you should not do is quietly leave it off afterwards — the value of the guardrail is that it is always on when nobody is thinking about it. ## Where it sits among the lifecycle arguments `create_before_destroy` changes the *order* of a replacement but does not exempt it from `prevent_destroy`, since a destroy is still part of the plan. `ignore_changes` can stop the replacement being planned at all, which is sometimes the pragmatic way past a guard that is firing on a cosmetic attribute. And unlike a `precondition`, `prevent_destroy` takes no expression and gives no custom message — it is a blunt yes/no.
- Why does prevent_destroy block a change you expected to be an ordinary update?Because the provider marked the attribute you changed as forcing replacement, and a replacement is a destroy followed by a create. The guard reacts to the destroy in the plan, not to your intent. Read the plan for `forces replacement` to see which attribute caused it.
- How is prevent_destroy different from a provider argument such as deletion_protection?`prevent_destroy` is enforced by Terraform when it builds the plan, so it only stops Terraform, and it must be a literal value. `deletion_protection` is enforced by the cloud API against every caller including the console, and being an ordinary resource argument it can be driven by a variable. Use both.
saying these in an interview costs you the question
- Thinks it skips the resource and lets the rest apply
- Believes it also stops deletion from the console
- Expects it to survive deleting the resource block
- Assumes an ordinary attribute change can never trip it
- Tries to set it from a variable such as var.is_prod