OpenTofu 1.12: Dynamic prevent_destroy, JSON-to-File Output, and the Feature Terraform Never Shipped in 10 Years
OpenTofu 1.12.0 ships dynamic prevent_destroy — wiring the lifecycle meta-argument to input variables — a feature Terraform users have requested since version 0.7 in 2016. The release also adds parallel provider installation, a -json-into CLI flag for dual-format output, and automatic lock file population on tofu init.
OpenTofu 1.12.0 shipped on May 14, 2026, and its marquee feature is one that HashiCorp had declined to implement for a decade. Dynamic prevent_destroy lets you wire the lifecycle meta-argument to an input variable:
resource "aws_rds_instance" "main" {
lifecycle {
prevent_destroy = var.protect_db
}
}
This sounds trivial. It is not. For ten years, Terraform users trying to write a single module deployable across dev, staging, and production hit the same wall: Variables may not be used here. This value does not accept expressions. The workaround was copy-pasting the entire module and toggling the flag manually — a maintenance nightmare that caused production deletion incidents when someone forgot to update the right copy.
OpenTofu 1.12 fixes it cleanly. You define one module, pass protect_db = true for production and protect_db = false for dev, and the state engine enforces it correctly. This is the canonical use case for multi-environment IaC, and it has been a documented feature request since Terraform 0.7 in 2016.
The second major addition is -json-into=FILENAME. When you run tofu plan or tofu apply, the flag writes machine-readable JSON to the specified file while keeping human-readable output in the terminal. Before this, getting JSON output required redirecting stdout and losing the formatted terminal display, or running two separate invocations. Platform engineering teams building custom CI dashboards, audit systems, or Slack notification pipelines benefit directly.
tofu init in 1.12 now automatically populates the dependency lock file with checksums for all platforms — both zh: (SHA-256 of zip) and h1: (tree hash) formats. Previously, initializing on one platform (macOS) left the lock file incomplete for CI runners on Linux, requiring a separate tofu providers lock -platform=linux_amd64 step. That step is now gone.
Provider installation is parallelized. If your project depends on 10+ providers, tofu init previously fetched them sequentially. 1.12 fetches all providers in parallel, which cuts initialization time significantly in large workspaces.
Two smaller but useful additions: destroy = false in the lifecycle block removes a resource from state without destroying the remote object — useful when you want to hand off a resource to another workspace or import it elsewhere. And the moved block now supports moving between modules with different schema versions, which was previously unsupported.
Breaking changes to note: WinRM provisioner support is deprecated (removal in 1.13), and official 32-bit (386/arm) builds are deprecated. If you’re distributing OpenTofu for 32-bit targets, plan for this.
OpenTofu remains a drop-in Terraform replacement — HCL syntax is unchanged, provider registry is compatible, and state files migrate without conversion. If you are on Terraform 1.x and have hit the prevent_destroy limitation, 1.12 is a direct reason to switch.