Skip to main content
RunBook Academy

TerraformXI · State Security and LifecycleLifecycle

Lifecycle Meta-Arguments in Depth

Advanced⏱ ~18 min🧪 Lab requiredbashterraform

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

Not yet marked complete on this device.

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_destroy
  • prevent_destroy
  • ignore_changes
  • replace_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:

  1. Destroys the old resource.
  2. Creates the new resource.

create_before_destroy = true reverses the order:

  1. Creates the new resource.
  2. 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-approve does 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_destroy does 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

  1. Q1. What is the role of sensitive = true?

  2. Q2. How should state secrets be managed?

  3. Q3. sensitive = true is sufficient for production secret management.

  4. Q4. How should the state backend be encrypted at rest?

  5. Q5. Which of the following are state security controls? (Select all that apply.)

  6. Q6. What is the role of OpenTofu client-side encryption?

  7. Q7. A credential was accidentally committed to the state. What is the first step?

Passing score: 75%. Answers are checked in this browser.