TrademarkTrademark
Features
Documentation
  1. Learning Center
  2. OpenTofu vs Terraform: The 2026 Comparison for IaC Teams

Article · part of a guide

Dynamic prevent_destroy in OpenTofu: Using a Variable to Protect Resources

Since OpenTofu 1.12, prevent_destroy can be set from an input variable instead of a hard-coded true or false. Here's how to use it, how it compares with Terraform, and where the new destroy = false lifecycle argument fits.

Key takeaways

  1. Starting with OpenTofu 1.12.0, released May 14, 2026, the prevent_destroy argument in a resource's lifecycle block can refer to other values in the same module, such as an input variable. Before 1.12 it had to be a literal true or false.
  2. The main use is shared modules: one module can block destruction of a production database while letting a development copy of the same database be destroyed and recreated freely.
  3. As of October 2026, Terraform still requires literal values in lifecycle blocks. Its v1.16 documentation says only literal values can be used because lifecycle processing happens too early for arbitrary expression evaluation.
  4. OpenTofu 1.12 also added a destroy lifecycle argument. Setting destroy = false makes OpenTofu forget an object, removing it from state without deleting the real infrastructure. Unlike prevent_destroy, destroy only accepts a constant true or false.
  5. prevent_destroy only protects a resource while its resource block stays in configuration. Deleting the block, or flipping the variable to false, removes the protection, so the variable that controls it needs the same review as the resource itself.

prevent_destroy has always had one awkward limitation: it's a hard-coded true or false. That's fine for a single root module, but it breaks down the moment the same module deploys a production database and a throwaway development copy. You want the first one locked down and the second one disposable, and a literal value can't do both.

OpenTofu 1.12.0, released May 14, 2026, removed that limitation. prevent_destroy can now refer to other values in the same module, including input variables. This post covers how it works, how it compares with Terraform, and the new destroy = false lifecycle argument that shipped in the same release.

Can prevent_destroy use a variable in OpenTofu?

Yes, from OpenTofu 1.12.0 onward. The prevent_destroy argument in a managed resource's lifecycle block can refer to other symbols in the same module, so prevent_destroy = var.protect_database is valid. On OpenTofu 1.11 and earlier, and in Terraform, the same line fails because lifecycle arguments only accept literal values.

Here's the pattern the OpenTofu team uses to introduce the feature. The module defaults to protected, so anyone who forgets to set the variable gets the safe behavior:

# modules/database/main.tf
 
variable "prevent_destroy_database" {
  type    = bool
  default = true
}
 
resource "aws_db_instance" "main" {
  identifier     = var.identifier
  engine         = "postgres"
  instance_class = var.instance_class
  # ...
 
  lifecycle {
    prevent_destroy = var.prevent_destroy_database
  }
}

The development environment opts out explicitly when it calls the module:

# environments/dev/main.tf
 
module "database" {
  source = "../../modules/database"
 
  identifier               = "app-dev"
  instance_class           = "db.t4g.micro"
  prevent_destroy_database = false
}

Production doesn't set the variable at all, so it inherits the default of true. Any plan that would destroy the production instance now fails with an error, while tofu destroy in dev tears the database down like any other resource.

Why couldn't prevent_destroy use variables before?

Lifecycle settings are processed very early, before OpenTofu or Terraform evaluates most expressions. Terraform's documentation still says so directly: as of its v1.16 docs, only literal values can be used in lifecycle because the processing happens too early for arbitrary expression evaluation. OpenTofu 1.12 reworked that for prevent_destroy specifically, and the 1.12 release announcement describes it as the first of several lifecycle settings the project hopes to make dynamic.

Requests for variable-driven prevent_destroy go back to Terraform 0.7 in 2016. Until this release, the usual answers were workarounds.

How do you get the same result in Terraform?

You can't set prevent_destroy from a variable in Terraform, as of Terraform 1.16 (the latest stable release in October 2026). Teams that need different behavior per environment generally use one of three workarounds:

  • Two resource blocks with mutually exclusive count. One block has prevent_destroy = true and is created when a flag is set. The other has prevent_destroy = false and is created otherwise. It works, but every reference to the resource needs a conditional, and switching an existing resource between the two blocks needs a moved block.
  • Separate modules or module copies per environment. This keeps the lifecycle literal but duplicates code.
  • Policy instead of lifecycle. A policy-as-code check that fails any plan deleting a protected resource type in production. This is a reasonable control in its own right, and some teams keep it even after moving to OpenTofu.

If you're comparing the two tools more broadly, our OpenTofu vs. Terraform guide covers the other places they've diverged.

What does lifecycle destroy = false do?

OpenTofu 1.12.0 also added a destroy argument to the lifecycle block of managed resources. When you set destroy = false, OpenTofu forgets the object instead of destroying it. If the resource is removed from configuration, replaced, or included in tofu destroy, the plan shows a forget action, the object is dropped from state, and the real infrastructure stays where it is.

resource "aws_s3_bucket" "audit_logs" {
  bucket = "acme-audit-logs"
 
  lifecycle {
    destroy = false
  }
}

That's useful when you're handing a resource to another team or tool, or retiring a configuration without touching what it built. A few details from the OpenTofu documentation matter in practice:

  • It only accepts a constant. Unlike prevent_destroy, destroy takes a literal true or false, not a variable.
  • It's stored in state. Once applied, OpenTofu won't plan a destroy for that object until you set it back to true or remove the argument. For a single-instance resource, a removed block with destroy = true can override it.
  • It overrides prevent_destroy. With destroy = false, prevent_destroy has no effect, because nothing is being destroyed.
  • tofu destroy exits non-zero. If forgotten resources mean the destroy can't fully finish, the command reports failure, which is worth knowing before you wire it into a teardown pipeline.
  • Re-adding the resource creates a new one. If you later put a forgotten resource back at the same address, OpenTofu tries to create it. Import the existing object instead if it's still there.

Terraform has a similar capability. Its removed block has supported lifecycle { destroy = false } for some time, and its current lifecycle documentation (v1.16) also lists a destroy argument for resources. So unlike dynamic prevent_destroy, this one isn't unique to OpenTofu. OpenTofu also supports destroy = false inside removed blocks.

What are the limits of prevent_destroy?

prevent_destroy is a guard against accidental plans, not a permission system. Two limits apply in both OpenTofu and Terraform:

  1. Removing the resource block removes the protection. The setting lives in the block, so deleting the block deletes the guard, and the next plan is free to destroy the object.
  2. It doesn't stop deletion outside OpenTofu. Someone with cloud console or API access can still delete the resource. Use the provider's own protection too, such as deletion_protection on an RDS instance.

Dynamic prevent_destroy adds a third consideration. When protection depends on a variable, changing that variable is now as consequential as changing the resource. A one-line edit setting prevent_destroy_database = false in production turns the guard off. Keep the default as true in the module, set the value centrally for production rather than per workspace, and review changes to it the same way you'd review a change to the database itself.

How does Scalr handle dynamic prevent_destroy?

In Scalr, the OpenTofu version is a per-workspace setting, so dynamic prevent_destroy works in any Scalr workspace set to OpenTofu 1.12 or later. Scalr's variable scoping fits this pattern: a variable set at the environment scope is inherited by every workspace in that environment, so a production environment can set the protection variable to true once instead of in every workspace. For shared groups of workspaces that cut across environments, variable sets do the same job.

For the policy-based backstop mentioned above, Scalr evaluates Open Policy Agent policies against the plan before apply. Our OPA series shows how to write simple policies of that kind.

For the full set of lifecycle arguments, including create_before_destroy, ignore_changes, and replace_triggered_by, see our guide to the lifecycle meta-argument.

Frequently asked questions

Can prevent_destroy use a variable?

In OpenTofu, yes, starting with version 1.12.0 (May 2026). prevent_destroy can be set to an input variable or another value in the same module, for example prevent_destroy = var.protect_database. In Terraform, no: as of Terraform 1.16, lifecycle arguments only accept literal values, and using a variable returns a "Variables may not be used here" error.

Which OpenTofu version added dynamic prevent_destroy?

OpenTofu 1.12.0, released May 14, 2026. The changelog describes it as allowing prevent_destroy in a managed resource's lifecycle block to refer to other symbols in the same module. Earlier OpenTofu versions, like Terraform, require a literal true or false.

Does prevent_destroy stop me from deleting a resource by removing it from my configuration?

No. prevent_destroy only applies while the resource block, and the setting inside it, is still in configuration. If you delete the resource block, the setting goes with it and the next plan can destroy the object. This is true in both OpenTofu and Terraform.

What does lifecycle destroy = false do in OpenTofu?

It tells OpenTofu to forget the object instead of destroying it. When the resource is removed, replaced, or targeted by tofu destroy, OpenTofu drops it from state and leaves the real infrastructure in place. It was added in OpenTofu 1.12.0 and only accepts a constant true or false.

Can I use dynamic prevent_destroy in Scalr?

Yes, on workspaces running OpenTofu 1.12 or later. Scalr lets you pick the OpenTofu version per workspace, and variables set at the account or environment scope are inherited by the workspaces in that scope, so a production environment can set the protection variable to true once for every workspace in it.

About the author

Ryan Fee

director of platform engineering at Scalr

Ryan Fee is the director of platform engineering at Scalr, with over 15 years of experience improving infrastructure experiences at companies large and small.

Part of this guide

2 sheets

OpenTofu vs Terraform: The 2026 Comparison for IaC Teams

1 article