Chapter 2: The Core Workflow
Topic 13

Lifecycle Meta-Arguments

Lifecycle

The lifecycle block overrides Terraform's default create/update/destroy behavior on a single resource. Its four settings — create_before_destroy, prevent_destroy, ignore_changes, and replace_triggered_by — are the tools for zero-downtime replacements, hard guardrails on critical resources, and making peace with values that other systems change behind Terraform's back.

Every one of these hides Terraform's default from the next person who reads the config, so each is a deliberate override that earns a comment, not a default to reach for. The three that get confused with each other solve genuinely different problems, which the comparison box below pulls apart.

The three settings people confuse
create_before_destroy
Reorders a replacement so the new object is built before the old is destroyed — zero downtime behind a load balancer.
prevent_destroy
A hard stop: any plan that would destroy the resource fails. A guardrail for databases and stateful stores.
ignore_changes
Stops Terraform reverting attributes something else owns at runtime, like autoscaler-managed capacity. Scope it, never use all.

create_before_destroy

By default, when a resource must be replaced, Terraform destroys the old object first and then creates the new one — a gap during which the resource does not exist. create_before_destroy = true inverts that: the replacement is built first, then the old one is destroyed once the new one is healthy. This is the key to replacing an instance behind a load balancer without an outage — the new instance comes up and registers before the old one leaves.

Zero-downtime replacement
resource "aws_instance" "web" {
  ami           = data.aws_ami.al2023.id
  instance_type = "t3.micro"

  lifecycle {
    create_before_destroy = true
  }
}

prevent_destroy

prevent_destroy = true is a hard stop: any plan that would destroy this resource fails outright, before apply. It is a guardrail for databases and stateful stores — the thing standing between a careless -target or a stray config change and a deleted production RDS instance. Its one blind spot is important: it protects the resource only while the block is in your configuration. Delete the resource block entirely and Terraform plans a destroy with nothing left to object — which is why you pair it with the cloud's own deletion protection.

A guardrail on a stateful resource
resource "aws_db_instance" "main" {
  identifier          = "prod-db"
  deletion_protection = true   # the cloud-side backstop

  lifecycle {
    prevent_destroy = true          # the Terraform-side guardrail
  }
}

ignore_changes

ignore_changes tells Terraform to stop fighting an attribute that something else legitimately changes — an autoscaler that adjusts desired_count, a deployment system that updates a tag, a process that rotates a value out of band. Without it, every plan wants to revert the external change and every apply undoes someone else's work. Scope it to the specific attributes that actually change externally; the temptation to write ignore_changes = all silences real drift along with the noise and is almost never right.

Stop reverting an out-of-band change
resource "aws_autoscaling_group" "app" {
  desired_capacity = 2
  min_size         = 2
  max_size         = 10

  lifecycle {
    # a scaling policy owns desired_capacity at runtime
    ignore_changes = [desired_capacity]
  }
}

replace_triggered_by

replace_triggered_by forces a resource to be replaced when a referenced resource or attribute changes, even when none of its own arguments changed. The use case is a resource that should be rebuilt whenever its dependency is — replacing an instance whenever a new launch configuration is created, for example. It takes a list of references to other resources or their attributes, and any change to one triggers the replacement.

Interactions and Ordering

create_before_destroy does not stay contained to one resource. If a resource set to create-before-destroy is depended on by others, Terraform must apply the same ordering up the chain so the dependents can attach to the new object before the old one is gone — which can force create-before-destroy onto resources you did not annotate, producing replacements you did not expect. The other interaction worth knowing: create-before-destroy collides with any resource that must have a unique name, like a fixed S3 bucket name, because the new object cannot be created while the old one still holds the name.

prevent_destroy vs ignore_changes vs create_before_destroy

prevent_destroy — blocks deletion entirely: any plan that would destroy the resource fails. A guardrail for databases and stateful stores. Pair it with cloud-side deletion protection.

ignore_changes — stops Terraform from reverting specific attributes that change out of band, like an autoscaler-managed capacity. Scope it to named attributes, never all.

create_before_destroy — reorders a replacement so the new object is built before the old is destroyed, avoiding downtime behind a load balancer. Use it for stateless resources, not ones with unique names.

Common Mistakes
  • Relying on prevent_destroy as the only protection for a production database, then having someone delete the resource block entirely — which bypasses it. Pair it with the resource's own deletion protection.
  • Using ignore_changes = all to silence plan noise and then missing real drift the config should have caught.
  • Adding create_before_destroy to a resource with a name that must be unique, like a fixed bucket name, so the new object collides with the old one and creation fails.
  • Forgetting that create_before_destroy propagates to dependent resources, producing replacements up the chain that you never annotated.
  • Leaving a lifecycle override uncommented, so the next reader cannot tell why the default behavior is hidden.
Best Practices
  • Use create_before_destroy on any stateless resource behind a load balancer so it can be replaced without an outage.
  • Set prevent_destroy on databases and stateful stores, and back it with the cloud's own deletion protection.
  • Scope ignore_changes to the specific attributes that legitimately change out of band, never all as a habit.
  • Comment every lifecycle override with the reason, since each one hides default behavior from the next reader.
  • Check whether a resource has a unique name before adding create_before_destroy, to avoid a creation collision.
Comparable tools CloudFormation DeletionPolicy and UpdateReplacePolicy Pulumi protect, ignoreChanges, and deleteBeforeReplace Ansible no direct equivalent — it does not model resource lifecycle

Knowledge Check

You set prevent_destroy = true on an RDS instance, then a teammate deletes the entire resource block and applies. What happens?

  • The database is destroyed — prevent_destroy only protects while the block is in the config, which is why you pair it with cloud-side deletion protection
  • The apply fails with a hard error because prevent_destroy still blocks the deletion regardless of the removed block
  • Terraform leaves the live database running but quietly drops its entry from the state file without deleting anything
  • Terraform automatically restores the deleted resource block back into the configuration files and re-runs the plan to honor the guardrail it originally carried

When does create_before_destroy cause a creation collision?

  • When the resource has a name that must be unique, so the new object cannot be created while the old one still holds the name
  • Whenever the resource happens to sit behind an application load balancer during the replacement
  • When the resource has no explicit depends_on declared on any of its upstream dependencies
  • When the AMI argument changes to a newer base image but the instance type stays exactly the same across the entire apply run

What is a legitimate use of ignore_changes?

  • Ignoring an autoscaler-managed desired_capacity so Terraform stops reverting the scaling policy's adjustments
  • Silencing every possible kind of configuration drift across a resource with ignore_changes = all just to keep noisy plans clean
  • Preventing a production database resource from ever being destroyed by an accidental apply
  • Replacing a resource automatically whenever one of its referenced dependencies changes

Which lifecycle setting reorders a replacement so the new object exists before the old one is destroyed?

  • create_before_destroy
  • prevent_destroy
  • ignore_changes
  • replace_triggered_by

What does replace_triggered_by do?

  • Forces a resource to be replaced when a referenced resource or attribute changes, even if its own arguments did not
  • Blocks the replacement of a resource until a referenced trigger condition is satisfied during the plan
  • Triggers a fresh read of the resource's current state from the provider API at the start of every single plan operation
  • Replaces the resource only while it is being torn down during a destroy operation

You got correct