4.03 Terraform Lifecycle Rules
Overview
Lifecycle rules let you override Terraform's default destroy-then-create behavior for a resource — controlling the order of replacement, preventing destruction entirely, or telling Terraform to ignore changes to specific attributes.
Abstract
Terraform exposes a lifecycle block inside a resource to customize how that resource is created, updated, or destroyed. The three core arguments are create_before_destroy, prevent_destroy, and ignore_changes.
Why It Matters in Production
By default, Terraform destroys a resource before recreating it when a change forces replacement — which can mean downtime for that resource during apply. Lifecycle rules give you control over that risk: reorder the operation so a replacement exists before the old one is removed, block destructive changes to critical resources like databases, or tolerate drift on attributes that are legitimately managed outside Terraform.
Key Concepts
| Argument | Order | Behavior |
|---|---|---|
create_before_destroy |
1 | Create the new resource first, then destroy the old one |
prevent_destroy |
2 | Blocks any apply that would destroy the resource (does not block terraform destroy) |
ignore_changes |
3 | Ignores changes to specific resource attributes (or all attributes) during apply |
Common Use Cases
- Using
create_before_destroyfor resources where brief unavailability during replacement is unacceptable (e.g. load balancer attachments, DNS records). - Using
prevent_destroyon stateful resources such as a production MySQL or PostgreSQL database to guard against accidental deletion via configuration changes. - Using
ignore_changes = [tags]when an external system (auto-tagging, a tagging Lambda, or another team) manages certain attributes outside of Terraform. - Using
ignore_changes = allfor resources that should be created once by Terraform but then left alone for any further modification.
Example Configuration or Commands
Default behavior — destroy before create
resource "local_file" "pet" {
filename = "/root/pets.txt"
content = "We love pets!"
file_permission = "0700"
}
#local_file.pet must be replaced
-/+ resource "local_file" "pet" {
content = "We love pets!"
directory_permission = "0777"
~ file_permission = "0777" -> "0700" # forces replacement
filename = "/root/pet.txt"
~ id = "5f8fb950ac60f7f23ef968097cda0a1fd3c11bdf" -> (known after apply)
}
Plan: 1 to add, 0 to change, 1 to destroy.
local_file.pet: Destroying...
[id=5f8fb950ac60f7f23ef968097cda0a1fd3c11bdf]
local_file.pet: Destruction complete after 0s
local_file.pet: Creating...
local_file.pet: Creation complete after 0s
[id=5f8fb950ac60f7f23ef968097cda0a1fd3c11bdf]
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
create_before_destroy
resource "local_file" "pet" {
filename = "/root/pets.txt"
content = "We love pets!"
file_permission = "0700"
lifecycle {
create_before_destroy = true
}
}
#local_file.pet must be replaced
-/+ resource "local_file" "pet" {
content = "We love pets!"
directory_permission = "0777"
~ file_permission = "0777" -> "0755" # forces replacement
filename = "/root/pet.txt"
~ id = "5f8fb950ac60f7f23ef968097cda0a1fd3c11bdf" -> (known after apply)
}
Plan: 1 to add, 0 to change, 1 to destroy.
local_file.pet: Creating...
local_file.pet: Creation complete after 0s
[id=5f8fb950ac60f7f23ef968097cda0a1fd3c11bdf]
local_file.pet: Destroying...
[id=5f8fb950ac60f7f23ef968097cda0a1fd3c11bdf]
local_file.pet: Destruction complete after 0s
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
Note the order flips: the new resource is created first, then the old one is destroyed.
prevent_destroy
resource "local_file" "pet" {
filename = "/root/pets.txt"
content = "We love pets!"
file_permission = "0700"
lifecycle {
prevent_destroy = true
}
}
local_file.my-pet: Refreshing state...
[id=cba595b7d9f94ba1107a46f3f731912d95fb3d2c]
Error: Instance cannot be destroyed
on main.tf line 1:
1: resource "local_file" "my-pet" {
Resource local_file.my-pet has lifecycle.prevent_destroy set, but the plan calls
for this resource to be destroyed. To avoid this error and continue with the
plan, either disable lifecycle.prevent_destroy or reduce the scope of the
plan using the -target flag.
Note
prevent_destroy only blocks destruction caused by configuration changes and apply. Running terraform destroy directly will still remove the resource.
ignore_changes — specific attribute
resource "aws_instance" "webserver" {
ami = "ami-0edab43b6fa892279"
instance_type = "t2.micro"
tags = {
Name = "ProjectA-Webserver"
}
}
After the instance is created, if the Name tag is changed outside Terraform (e.g. manually in the AWS console) to ProjectB-Webserver, a plain terraform apply will revert it:
~ resource "aws_instance" "webserver" {
~ tags = {
~ "Name" = "ProjectB-Webserver" -> "ProjectA-Webserver"
}
}
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
To make Terraform tolerate that external change instead of reverting it:
resource "aws_instance" "webserver" {
ami = "ami-0edab43b6fa892279"
instance_type = "t2.micro"
tags = {
Name = "ProjectA-Webserver"
}
lifecycle {
ignore_changes = [
tags
]
}
}
aws_instance.webserver: Refreshing state... [id=i-05cd83b221911acd5]
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
ignore_changes — multiple attributes or all
To ignore every attribute instead of listing them individually:
Best Practices
Best Practices
- Use
create_before_destroyfor resources where availability during replacement matters, such as load-balanced compute instances. - Apply
prevent_destroyto stateful, hard-to-recreate resources like production databases or persistent storage volumes. - Scope
ignore_changesto the specific attributes that are genuinely managed externally — avoidallunless the resource is truly meant to be hands-off after creation. - Document why a lifecycle rule was added directly above the block, since the reasoning isn't always obvious to future readers.
Security Best Practices
Security
prevent_destroydoes not stopterraform destroy— pair it with state locking, restricted destroy permissions, or a separate approval step for critical resources.- Be cautious with
ignore_changes = allon security-relevant attributes (security groups, IAM policies, encryption settings) — drift there can go unnoticed indefinitely. - Review
ignore_changesusage periodically; attributes ignored for a temporary reason can become permanent blind spots in your IaC if forgotten.
Do and Don't
| ✅ Do | ❌ Don't |
|---|---|
Use create_before_destroy for zero-downtime replacements |
Assume every resource replacement is safe to do destroy-first |
Apply prevent_destroy to irreplaceable stateful resources |
Rely on prevent_destroy alone to protect against terraform destroy |
Scope ignore_changes to specific attributes |
Use ignore_changes = all without understanding what drift it hides |
| Comment why a lifecycle rule exists | Leave lifecycle rules undocumented for future maintainers to guess at |
Common Mistakes
Common Mistakes
- Forgetting that
prevent_destroystill allowsterraform destroyto remove the resource. - Setting
ignore_changes = allto silence noisy drift instead of identifying which specific attribute is actually changing externally. - Not testing
create_before_destroywith resources that have unique-name constraints, which can cause a conflict if both old and new resources briefly coexist.
Troubleshooting
# See whether a planned change will force replacement
terraform plan
# Confirm a resource still exists after a prevent_destroy-blocked apply
terraform show
# Check if ignore_changes is suppressing a diff you expected to see
terraform plan -refresh-only
Real-World Examples
E-commerce Platform — Zero-Downtime Instance Replacement
Scenario: A retailer running compute instances behind a load balancer needed to update an AMI without downtime.
Problem: The default destroy-then-create order briefly removed capacity from the pool during every AMI update.
Solution: Added create_before_destroy = true to the instance resource so a new instance was provisioned and attached before the old one was terminated.
Outcome: AMI rollouts no longer caused a capacity dip, and rollouts could happen during business hours without customer impact.
Financial Services Team — Accidental Database Deletion Averted
Scenario: A team managing a production PostgreSQL instance via Terraform.
Problem: A configuration change unintentionally forced replacement of the database resource, which would have caused data loss.
Solution: prevent_destroy = true was already set on the resource, so Terraform refused the apply and surfaced an explicit error instead of silently destroying the database.
Outcome: The risky change was caught and reworked before any data loss occurred.
Cloud Ops Team — Tolerating Externally-Managed Tags
Scenario: A cost-allocation tool automatically updated tags on EC2 instances outside of Terraform.
Problem: Every terraform apply reverted the tool's tag updates back to the Terraform-defined values, fighting with the external system.
Solution: Added ignore_changes = [tags] to the instance resource so Terraform stopped reverting externally-managed tags.
Outcome: The cost-allocation tool and Terraform coexisted without conflicting on every apply.
Quick Recap
- Lifecycle rules live inside a
lifecycle { }block within a resource definition. create_before_destroy = truecreates the replacement resource before destroying the old one.prevent_destroy = trueblocks destruction via configuration changes and apply, but not viaterraform destroy.ignore_changesaccepts a list of specific attributes, or the keywordall, to ignore external changes to those attributes.- These rules are commonly used with databases, load-balanced compute, and resources with externally-managed attributes.
Interview / Revision Notes
- Q: What are the three core lifecycle arguments in Terraform?
create_before_destroy,prevent_destroy, andignore_changes. - Q: Does
prevent_destroystopterraform destroy? No — it only blocks destruction triggered by configuration changes andapply. - Q: What does
create_before_destroychange about resource replacement? It creates the new resource first, then destroys the old one, instead of the default destroy-first order. - Q: How do you ignore changes to every attribute of a resource? Set
ignore_changes = allinside thelifecycleblock. - Q: Why would you use
ignore_changeson tags? To prevent Terraform from reverting tag changes made by an external system, such as an auto-tagging tool.