Skip to content

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_destroy for resources where brief unavailability during replacement is unacceptable (e.g. load balancer attachments, DNS records).
  • Using prevent_destroy on 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 = all for 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"
}
terraform apply
#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
  }
}
terraform apply
#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
  }
}
terraform apply
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
    ]
  }
}
terraform apply
aws_instance.webserver: Refreshing state... [id=i-05cd83b221911acd5]

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

ignore_changes — multiple attributes or all

lifecycle {
  ignore_changes = [
    tags, ami
  ]
}

To ignore every attribute instead of listing them individually:

lifecycle {
  ignore_changes = all
}

Best Practices

Best Practices

  • Use create_before_destroy for resources where availability during replacement matters, such as load-balanced compute instances.
  • Apply prevent_destroy to stateful, hard-to-recreate resources like production databases or persistent storage volumes.
  • Scope ignore_changes to the specific attributes that are genuinely managed externally — avoid all unless 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_destroy does not stop terraform destroy — pair it with state locking, restricted destroy permissions, or a separate approval step for critical resources.
  • Be cautious with ignore_changes = all on security-relevant attributes (security groups, IAM policies, encryption settings) — drift there can go unnoticed indefinitely.
  • Review ignore_changes usage 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_destroy still allows terraform destroy to remove the resource.
  • Setting ignore_changes = all to silence noisy drift instead of identifying which specific attribute is actually changing externally.
  • Not testing create_before_destroy with 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 = true creates the replacement resource before destroying the old one.
  • prevent_destroy = true blocks destruction via configuration changes and apply, but not via terraform destroy.
  • ignore_changes accepts a list of specific attributes, or the keyword all, 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, and ignore_changes.
  • Q: Does prevent_destroy stop terraform destroy? No — it only blocks destruction triggered by configuration changes and apply.
  • Q: What does create_before_destroy change 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 = all inside the lifecycle block.
  • Q: Why would you use ignore_changes on tags? To prevent Terraform from reverting tag changes made by an external system, such as an auto-tagging tool.