On this page · 13 sections
- The version confusion, settled first
- Action triggers grow up: halt, taint, continue
- Import blocks inside modules
- Everything else in the beta, ranked by whether you will notice
- What 1.15 already gave you, and what it did not
- The cost side nobody puts in the release notes
- Where this sits next to OpenTofu
- The agent angle, and why lifecycle rules suddenly matter more
- India-specific considerations
- An upgrade plan you can actually run
- FAQ
- How eCorpIT can help
- References
Summary. Terraform 1.16.0-beta1 was published on 23 July 2026, roughly three months after Terraform 1.15.0 reached general availability on 29 April 2026 and 1.15.1 followed on 1 May 2026. The beta adds 6 new features and 12 enhancements to the HashiCorp changelog, and the one drawing the most attention is a single line: resource lifecycle blocks now support destroy = false. That rule is widely and wrongly attributed to Terraform 1.14. In Terraform 1.15.x, destroy is documented only inside a removed block, so a team that pins ~> 1.15 and copies a destroy = false snippet into a resource block gets an error, not protection. The other changes that matter to production teams are on_failure modes of halt, taint or continue for action triggers, import blocks inside modules, before_destroy and after_destroy action events, terraform graph -format=mermaid, and -json output for state show and workspace list. None of this is free to adopt: HCP Terraform bills on managed resources, and Spacelift's pricing analysis, updated 6 August 2026, puts the Essentials rate at $0.0001359 per managed resource per hour, or about $978.48 a month for 10,000 resources running 24x7.
That last number is why this release deserves a careful read rather than a skim. Every new lifecycle rule changes what sits in state, and what sits in state is what you pay for.
The version confusion, settled first
Three separate claims are circulating about destroy = false, and only one of them is correct.
The correct one comes from the Terraform v1.16.0-beta1 release notes, dated 23 July 2026, which list under ENHANCEMENTS: "Resource lifecycle blocks now support destroy = false to prevent a resource from being destroyed." The change is tracked as issue 38784.
The incorrect ones say the rule arrived in 1.14 or 1.15. Check the shipped documentation and the claim collapses. The lifecycle meta-argument reference for v1.15.x, the version marked "latest" on HashiCorp Developer as of 9 August 2026, describes destroy in one sentence: "Set to false to remove a resource from state without destroying the actual infrastructure resource. You can only use this rule in removed block." Same keyword, different block, opposite intent. In a removed block it means "forget this resource without deleting it". In a resource block, from 1.16, it means "refuse to delete this resource".
If you are running 1.15.x today, the protection you have is prevent_destroy, and it has a documented hole. HashiCorp's own reference states that Terraform "does not explicitly record a resource's lifecycle rule to state" except for create_before_destroy, so "Terraform destroys the actual infrastructure during an apply operation if you remove the resource's configuration, even if prevent_destroy is enabled." Delete the block, lose the guard. That is the failure mode most teams have actually hit.
| Rule | Introduced | Valid in | What it does | Survives config deletion |
|---|---|---|---|---|
prevent_destroy |
Pre-1.0 | resource |
Rejects plans that would destroy the object | No — removing the block removes the guard |
destroy = false |
1.16.0-beta1 (23 Jul 2026) | resource |
Prevents the resource from being destroyed | Not yet documented for GA |
destroy = false |
Earlier | removed |
Drops the resource from state, leaves infrastructure alive | Not applicable |
create_before_destroy |
Pre-1.0 | resource |
Creates the replacement before destroying the original | Yes — recorded in state |
ignore_changes |
Pre-1.0 | resource |
Skips update planning for named attributes | No |
The practical read: treat destroy = false as a 1.16 feature, keep prevent_destroy on your stateful resources until 1.16 is GA in your pipeline, and pair either one with a removed block workflow when you genuinely need to hand a resource to another owner.
Action triggers grow up: halt, taint, continue
Terraform actions let a provider expose an imperative operation that runs outside normal create, read, update and delete. You bind them with an action_trigger block inside lifecycle. Per the v1.15.x reference, action_trigger takes three arguments: events (required list), condition (optional expression) and actions (required ordered list). In 1.15.x the documented events are before_create, after_create, before_update and after_update.
The 1.16 beta extends this in two ways that change how you design a pipeline. First, actions can now use before_destroy and after_destroy events, tracked as issue 38668. Second, and more consequential, "Resource action triggers can now use on_failure modes of halt, taint, or continue" (issue 38722).
Before 1.16, a failed action left you with one behaviour and no dial. Now the failure semantics are yours to choose:
resource "aws_ecs_service" "api" {
name = "api"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.api.arn
lifecycle {
action_trigger {
events = [after_update]
on_failure = halt
actions = [action.aws_lambda_invoke.smoke_test]
}
action_trigger {
events = [after_create]
on_failure = continue
actions = [action.aws_lambda_invoke.warm_cache]
}
}
}
The judgement call is which mode belongs where. A smoke test that fails should stop the run, so halt. A cache warm that fails is noise, so continue. taint sits between them: the action failed, the resource is suspect, mark it for replacement on the next apply rather than pretending the deploy was clean. Getting this wrong in either direction is expensive — continue on a verification step ships broken infrastructure quietly, and halt on a best-effort side task blocks releases for no reason.
Two more action changes in the beta are worth knowing. Action configurations in Stacks now have access to a caller symbol containing the object value of the calling resource (issue 38668), and -invoke can be combined with -target to specify the calling resource instance when multiple resources trigger the same action (issue 38845). The beta also fixes a real bug here: "Actions are now invoked with respect to all resource dependencies." If you built anything on actions in 1.15, re-read your dependency assumptions.
There is also a crash fix carried in 1.15.1, released 1 May 2026: "Fixed crash when configuration has an invalid action_trigger nested block in data or ephemeral lifecycle blocks" (issue 38402). Teams on 1.15.0 who are experimenting with actions should take the patch.
Import blocks inside modules
import blocks inside modules are now supported (issue 38352). This is the change with the widest blast radius for teams that inherited infrastructure created by hand, by another tool, or by an earlier IaC generation.
Until now, an import block had to live in the root module. If your platform team distributes a module for, say, a standard VPC layout, and a business unit already has that VPC clicked into existence, the import had to be written at the root, referencing addresses inside the module. That works, but the knowledge of what to import lives in the wrong place — with the consumer, not the module author.
# modules/network/import.tf
variable "existing_vpc_id" {
type = string
default = null
}
import {
for_each = var.existing_vpc_id == null ? [] : [var.existing_vpc_id]
to = aws_vpc.main
id = each.value
}
The beta pairs this with two related fixes: "import blocks now correctly respect provider local names" (issue 38338) and "Terraform now returns the correct error when an import target exists in state but has no corresponding configuration" (issue 38782). The second one matters if you have ever chased a confusing message during a brownfield migration.
One planning note for cost. Import brings resources under management, and under HCP Terraform's Resources Under Management model, that is a billing event. Spacelift's analysis notes that a resource is counted "if it's in an HCP-Terraform-managed state file and in the state it has mode = "managed"", starting from the first terraform plan or terraform apply, and that null_resource and terraform_data are excluded. Security group rules and S3 lifecycle rules each count individually. A brownfield import of a mature AWS account can add thousands of billable resources in one afternoon.
Everything else in the beta, ranked by whether you will notice
| Change | Type | Who it affects | Effort to adopt |
|---|---|---|---|
destroy = false in resource lifecycle |
Enhancement | Anyone protecting stateful resources | Low — one line, but wait for GA |
on_failure modes for action triggers |
Feature | Teams already using actions | Medium — requires a policy per trigger |
import blocks inside modules |
Feature | Module authors, brownfield migrations | Medium — module redesign |
terraform graph -format=mermaid |
Enhancement | Anyone documenting dependencies | Low |
-json for state show and workspace list |
Enhancement | CI/CD and tooling authors | Low |
terraform console -scope=<module> |
Enhancement | Anyone debugging module internals | Low |
terraform_data store block |
Feature | Teams handling ephemeral secrets | Medium |
| Planned private data for providers | Feature | Provider developers | None for consumers |
| Linux s390x (zLinux) binary | Feature | Mainframe-adjacent shops | Low |
contains() accepts null |
Enhancement | Anyone writing defensive HCL | Low |
bastion_host_key now applied by provisioners |
Upgrade note | Anyone using SSH provisioners | Check before upgrading |
Three of these deserve a sentence each.
terraform graph -format=mermaid (issue 38719) is the smallest change with the best return. Terraform's DOT output has always needed Graphviz to be useful. Mermaid renders natively in GitHub, GitLab and most documentation platforms, so a dependency graph can now live in a pull request description instead of a stale wiki page.
terraform console -scope=<module address> (issue 31861) closes a request that has been open for years. Debugging an expression inside a module previously meant reproducing the module's variable context by hand in the root console. Now you point the console at the module instance.
The store block in terraform_data (issue 38298) can hold ephemeral and sensitive values across plan and apply. Combined with the provider private-data change (issue 37986), this is the beta's quiet theme: state is becoming more able to carry things it previously could not, without those things landing in the state file as plaintext.
And read the upgrade note before you touch a bastion: "bastion_host_key is now correctly applied by provisioners. Review your provisioner configurations to verify the configured key is correct before upgrading" (issue 38318). A key that was silently ignored is now enforced. Configurations that "worked" because the value was wrong will start failing.
What 1.15 already gave you, and what it did not
A fair amount of the confusion about 1.16 comes from people conflating it with 1.15, which shipped on 29 April 2026 and was covered in a HashiCorp post on 13 May 2026 by Apoorva Murthy and Jacob Plicque.
Terraform 1.15's headline items were dynamic module sources, variable and output deprecation, and inline type conversion. On dynamic sources, the HashiCorp post states: "With the Terraform 1.15 release, practitioners can now use variables within Terraform dependencies, encompassing aspects such as module sources and versions." The mechanism is a new const attribute on variables that signals whether the variable can be used during terraform init. It cannot be combined with sensitive or ephemeral.
variable "folder" {
type = string
const = true
}
module "zoo" {
source = "./${var.folder}"
}
The deprecation feature is the one platform teams under-use. A deprecated attribute on a variable or output emits a warning diagnostic during validation, which gives module authors a way to retire an interface without breaking every consumer on the same day. Root-module outputs are stricter: using a deprecated attribute there produces an error, not a warning.
1.15 also brought native Windows ARM64 binaries, S3 backend authentication via aws login (AWS CLI v2.32.0 and later), explicit type constraints on output blocks, functions inside mock blocks in the test framework, and validation blocks for Stacks variables.
What 1.15 did not bring is destroy = false on resources, on_failure, module-level imports or Mermaid graphs. If a blog post tells you otherwise, check the changelog for the tag you are pinning.
The cost side nobody puts in the release notes
Every feature above changes what Terraform manages, and HCP Terraform charges per managed resource. Independent pricing analysis published by Spacelift and updated on 6 August 2026 reports the current shape: a Free tier capped at 500 managed resources per organisation with 1 concurrent run, then Essentials at about $0.10 per managed resource per month, Standard at about $0.47 and Premium at about $0.99. Billing runs on hourly peak managed resources, with partial hours billed as full hours.
| Scale | Plan | Reported rate | Monthly cost |
|---|---|---|---|
| Under 500 resources | Free | $0 | $0 |
| 1,000 resources | Standard | $0.47 per resource-month | About $470 |
| 1,000 resources | Premium | $0.99 per resource-month | About $990 |
| 10,000 resources | Essentials | $0.0001359 per resource-hour | $978.48 (30-day month) |
| 10,000 resources | Standard | $0.47 per resource-month | About $4,700 |
| Self-managed | Terraform Enterprise | From $15,000/year | Five workspaces included |
Flavius Dinu, Developer Advocate at Mirantis and a Docker Captain, writing in Spacelift's 2026 pricing breakdown, sums up the complaint bluntly: "The key fear for customers is the inability to predict their Terraform Cloud bills". He is writing for a competitor, so weigh it accordingly, but the mechanism he describes is documented behaviour, not opinion — a per-resource meter that counts each security group rule separately is hard to forecast when a module refactor can double the resource count without changing a single piece of infrastructure.
HashiCorp's own pricing page, checked on 9 August 2026, advertises a $500 starting credit across the IBM HashiCorp Cloud Platform and routes detailed rates to an IBM pay-as-you-go pricing table. The legacy Free plan reached end of life on 31 March 2026, with remaining organisations moved to the enhanced Free tier of 500 managed resources and unlimited users.
The engineering conclusion: before you import a brownfield estate under a module-level import block, count what you are about to put in state. The real cost is usually the migration, not the code.
Where this sits next to OpenTofu
Teams that forked to OpenTofu after the licence change have their own feature ladder. OpenTofu 1.11.0 introduced ephemeral values, ephemeral resources and write-only attributes, plus an enabled meta-argument for resources and modules alongside count and for_each. It added S3 state storage in the eusc-de-east-1 region of the AWS European Sovereign Cloud, an azure_vault key provider for state and plan encryption keys, and dropped SHA-1 signatures in TLS handshakes per RFC 9155. The 1.11.x series is supported until 1 August 2026, with 1.12 now current.
The two projects are diverging on lifecycle design rather than converging. OpenTofu's enabled meta-argument and Terraform's destroy = false solve adjacent problems in different places in the language. If you are still deciding, our comparison of Terraform vs OpenTofu for 2026 standardisation walks the migration path in detail.
The agent angle, and why lifecycle rules suddenly matter more
On 5 August 2026, HashiCorp published a post arguing that HCP Terraform is the control plane for AI-driven infrastructure. Its authors, Simon Lynch and Aaron Evans of HashiCorp, describe the shift precisely: "Code that took days now takes seconds, so the bottleneck has moved from authoring to verification and approval."
That reframes every rule in this article. A destroy = false on a production database is a guardrail against a human typo today. Against an agent running a plan-execute-observe loop at machine speed, it is one of the few things standing between a hallucinated refactor and a deleted RDS instance. The HashiCorp post is blunt about the risk: "Without guardrails, an AI agent with write access to your infrastructure will amplify every gap in your IaC maturity through hallucinated output, ungated changes, over-broad access, and unbounded blast radius."
The post lays out five control layers — provenance, policy, identity, isolation and audit — and notes that mandatory policies cannot be bypassed by an agent lacking policy-admin permissions, because the gate lives in the platform layer rather than in the agent. Its operating rule is worth copying into your own runbook: "The agent can propose. It should not decide."
For teams building agent-assisted delivery pipelines, this connects directly to how you govern AI coding agent rollouts and how you structure release engineering and CI/CD platforms.
India-specific considerations
Indian platform teams face two pressures at once here. The first is cost. A rupee-denominated budget against a dollar-denominated per-resource meter means the effective HCP Terraform bill moves with the exchange rate on top of resource growth. At roughly ₹88 to the dollar in August 2026, a 10,000-resource Essentials bill of $978.48 a month lands near ₹86,000 a month before tax, for tooling that manages infrastructure you are already paying a cloud provider for. Many Indian teams run a self-hosted CI runner plus S3 or Azure Blob state instead, and spend the difference on engineers.
The second is data residency. Under the Digital Personal Data Protection Act 2023, where your Terraform state lives is a real question, because state files routinely contain identifiers, connection strings and occasionally worse. The 1.16 store block in terraform_data and the provider private-data change help keep sensitive values out of plaintext state, but they do not change where the state file physically sits. If you are designing for residency, decide the backend region before you decide the feature set. Our guide to cutting cloud spend for Indian teams covers the cost half of that trade-off, and Azure landing zone and Terraform IaC modernisation covers the architecture half.
An upgrade plan you can actually run
The beta is a beta. Do not put 1.16.0-beta1 in a production pipeline. Do use it now to find out what will break in three months.
- Pin your current version explicitly in
required_version. A range like>= 1.15will pull the 1.16 GA the day it lands.
- Run
terraform planagainst a copy of production state with the 1.16 beta binary in a scratch workspace. Compare plan output for drift.
- Audit every
provisionerblock forbastion_host_key. The upgrade note is explicit that behaviour changed.
- Inventory your
action_triggerblocks and assign each anon_failuremode deliberately. The default is not a decision.
- If you plan to use module-level
import, count the resources first and price them against your HCP Terraform tier.
- Replace Graphviz steps in documentation pipelines with
terraform graph -format=mermaidonce you are on 1.16.
- Keep
prevent_destroyin place untildestroy = falseships in a GA release and you have tested what happens when the block is removed.
If you are also planning a provider upgrade in the same window, sequence it separately. Our write-up on the AzureRM 5.0 breaking changes and migration explains why stacking a core upgrade and a provider major on the same change window turns two tractable problems into one intractable one. The same logic applies to cluster work such as a Kubernetes 1.35 and containerd 2.0 migration.
FAQ
How eCorpIT can help
eCorpIT is a CMMI Level 5 and ISO 27001:2022 certified engineering organisation in Gurugram, and our platform teams run Terraform upgrades, provider major migrations and brownfield state imports as scoped engagements rather than as background work squeezed between sprints. We size the managed-resource count before an import so the tooling bill does not arrive as a surprise, and we sequence core, provider and cluster upgrades so a single change window fails in only one way. If you are planning a 1.16 adoption or an IaC consolidation, talk to our engineering team.
References
- Release v1.16.0-beta1 - hashicorp/terraform - GitHub, 23 July 2026.
- Release v1.15.1 - hashicorp/terraform - GitHub, 1 May 2026.
- lifecycle meta-argument reference - HashiCorp Developer, v1.15.x documentation.
- New in Terraform 1.15: Dynamic sources, variable deprecation, and more - Apoorva Murthy and Jacob Plicque, HashiCorp, 13 May 2026.
- HCP Terraform is the control plane for AI-driven infrastructure - Simon Lynch and Aaron Evans, HashiCorp, 5 August 2026.
- Terraform Cloud/Enterprise Pricing - Tiers Overview 2026 - Flavius Dinu, Spacelift, updated 6 August 2026.
- HashiCorp Product Pricing - HashiCorp, accessed 9 August 2026.
- Releases - hashicorp/terraform - GitHub release index.
- What's new in OpenTofu - OpenTofu documentation.
- Releases - opentofu/opentofu - GitHub.
- Invoke actions with Terraform - HashiCorp Developer tutorial.
- resource block reference - HashiCorp Developer.
Last updated: 9 August 2026.