TerraformXI · State Security and LifecycleLifecycle
Lifecycle Meta-Arguments in Depth
What you'll learn
- Use create_before_destroy to control replacement order
- Use prevent_destroy as a circuit breaker for critical resources
- Use ignore_changes sparingly and with documentation
- Use replace_triggered_by to make replacement explicit
Prerequisites
Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-12
The lifecycle meta-argument is the place where you, as the
operator, override the providers default replacement
behaviour. It is the surgical control for the resource. The
four lifecycle arguments are:
create_before_destroyprevent_destroyignore_changesreplace_triggered_by
Each one has a specific production role. Each one can be misused. This lesson teaches the role and the misuse.
The lifecycle block
resource "aws_instance" "web" {
ami = "ami-0e1bed4f"
instance_type = "t3.medium"
lifecycle {
create_before_destroy = true
prevent_destroy = false
ignore_changes = [
tags["LastReviewed"],
]
replace_triggered_by = [
aws_ami.ubuntu.id,
]
}
}
The lifecycle block lives inside the resource block. It is
valid for every resource type.
create_before_destroy
By default, when a resource must be replaced, Terraform:
- Destroys the old resource.
- Creates the new resource.
create_before_destroy = true reverses the order:
- Creates the new resource.
- Destroys the old resource.
The reversal is appropriate when:
- The resource is part of a cluster that can tolerate short-term over-provisioning.
- The new resource must be validated before the old resource is destroyed.
- The destruction would cause downtime that the new resource can avoid.
The reversal is not a substitute for careful planning. A replace is a replace. The reversal just changes the order.
prevent_destroy
prevent_destroy = true is a circuit breaker:
resource "aws_db_instance" "primary" {
# ...
lifecycle {
prevent_destroy = true
}
}
When prevent_destroy = true is set:
- A plan that does not affect the resource succeeds.
- A plan that would destroy the resource fails with an error:
Error: Instance cannot be destroyed. - The destroy error is unconditional — even
-auto-approvedoes not override it.
prevent_destroy does not prevent:
- Manual destruction of the real-world resource outside Terraform.
- State manipulation that removes the resource from state.
- Resource replacement (the resource is destroyed and
recreated, which
prevent_destroydoes NOT prevent).
For production-critical resources, prevent_destroy is a
mandatory setting. The course has a dedicated lab in Part
CXI.
ignore_changes
ignore_changes tells Terraform to ignore specific attributes
when computing the plan:
resource "aws_instance" "web" {
ami = "ami-0e1bed4f"
instance_type = "t3.medium"
tags = {
Name = "web-01"
}
lifecycle {
ignore_changes = [
tags["LastReviewed"],
]
}
}
The tags["LastReviewed"] attribute is read from the state,
not from the configuration. The plan does not propose to
update the tag.
The legitimate use cases:
- An attribute is managed outside Terraform.
- An attribute is auto-computed by the provider.
- A computed attribute that the operator expects to drift.
The illegitimate use cases:
- Hiding a configuration error.
- Disabling plan review.
- Working around a provider bug.
replace_triggered_by
replace_triggered_by is the modern replacement for the
legacy taint mechanism. It allows a resource to be replaced
when another resources attribute changes:
resource "aws_instance" "web" {
ami = "ami-0e1bed4f"
instance_type = "t3.medium"
lifecycle {
replace_triggered_by = [
aws_ami.ubuntu.id,
]
}
}
When aws_ami.ubuntu.id changes, the aws_instance.web is
replaced during the next apply. The replacement is explicit;
the plan shows it.
The legitimate use cases:
- An attribute change in a dependency should force a replacement.
- The providers default replacement rules miss a relationship that the operator knows about.
The illegitimate use cases:
- Working around a providers intentional “no replace” rule.
- Cascading replacements that affect many resources.
lifecycle.precondition
The precondition block inside lifecycle lets you assert
conditions before the apply:
resource "aws_instance" "web" {
ami = "ami-0e1bed4f"
instance_type = "t3.medium"
lifecycle {
precondition {
condition = data.aws_region.current.name == "us-east-1"
error_message = "This configuration must be applied in us-east-1."
}
}
}
The precondition is evaluated at plan time. The plan fails if the condition is false.
The legitimate use cases:
- Verify the environment before destroying a resource.
- Verify the credentials before creating a resource.
- Verify the dependency before replacing a resource.
The course has a dedicated lesson on assertions (Part LXXX).
lifecycle.postcondition
The postcondition block is evaluated after the apply:
resource "aws_instance" "web" {
ami = "ami-0e1bed4f"
instance_type = "t3.medium"
lifecycle {
postcondition {
condition = self.tags["Owner"] != null
error_message = "The Owner tag must be set after apply."
}
}
}
The postcondition is evaluated after the apply. The plan succeeds if the condition is true. The apply fails if the condition is false.
The legitimate use cases:
- Verify the resource was created with the expected attributes.
- Verify the resource is in the expected state after the apply.
The lifecycle as a contract
The lifecycle meta-arguments are the contract between the operator and the provider. The contract says:
- The replacement order is X.
- The replacement triggers are Y.
- The destruction is Z.
- The ignored attributes are W.
The contract is the unit of review. A reviewer can read the contract and understand the operational implications.
What comes next
The next lesson is state fundamentals — the deepest production topic in the course.
Verification
Knowledge check · 7 questions
Q1. What is the role of sensitive = true?
Q2. How should state secrets be managed?
Q3. sensitive = true is sufficient for production secret management.
Q4. How should the state backend be encrypted at rest?
Q5. Which of the following are state security controls? (Select all that apply.)
Q6. What is the role of OpenTofu client-side encryption?
Q7. A credential was accidentally committed to the state. What is the first step?
Passing score: 75%. Answers are checked in this browser.