Skip to content
Infrastructure as Code iac resources 5 min read

The lifecycle Meta-Argument

By default, Terraform plans changes in a predictable order: it creates new resources, updates existing ones in place when possible, and destroys resources that have been removed from configuration. The lifecycle block is a nested meta-argument that lets you override these default behaviors per resource. It is the tool you reach for when the default create/update/destroy sequence would cause downtime, destroy something you cannot afford to lose, or fight endlessly with changes made outside Terraform. Because it is a meta-argument, lifecycle works on every resource type and every provider, and it behaves identically in OpenTofu.

Where lifecycle lives

The lifecycle block is declared inside a resource block. Its arguments accept literal values only — you cannot reference other resources or variables inside most of them, because Terraform evaluates lifecycle settings very early, before the dependency graph is fully resolved.

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"

  lifecycle {
    create_before_destroy = true
    prevent_destroy       = false
    ignore_changes        = [tags["LastScanned"]]
  }
}

create_before_destroy

When a change forces a resource to be replaced — for example, changing an ami or instance_type that cannot be updated in place — Terraform’s default is to destroy the old resource first, then create the new one. That leaves a window with no resource at all. Setting create_before_destroy = true inverts the order: the replacement is created first, then the old resource is destroyed once the new one is healthy. This is the foundation of zero-downtime replacement.

resource "aws_launch_template" "app" {
  name_prefix   = "app-"
  image_id      = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.small"

  lifecycle {
    create_before_destroy = true
  }
}

Using name_prefix instead of a fixed name matters here: if both the old and new resource must briefly coexist, a hard-coded unique name would collide and the apply would fail.

Output:

  # aws_launch_template.app must be replaced
+/- resource "aws_launch_template" "app" {
      ~ id            = "lt-08a1f..." -> (known after apply)
      ~ instance_type = "t3.micro" -> "t3.small"
    }

Plan: 1 to add, 0 to change, 1 to destroy.

Tip: create_before_destroy propagates. If resource A depends on resource B and A is set to create-before-destroy, Terraform will also apply create-before-destroy semantics to B during replacement so the dependency stays valid.

prevent_destroy

prevent_destroy = true is a safety guard. When set, any plan that would destroy the resource — whether through terraform destroy, removing it from configuration, or a forced replacement — fails with an error instead of proceeding. Use it on stateful, hard-to-recreate resources like production databases, S3 buckets holding data, or KMS keys.

resource "aws_db_instance" "primary" {
  identifier        = "prod-primary"
  engine            = "postgres"
  engine_version    = "16.3"
  instance_class    = "db.r6g.large"
  allocated_storage = 100

  lifecycle {
    prevent_destroy = true
  }
}

Output:

Error: Instance cannot be destroyed

  on main.tf line 1:
   1: resource "aws_db_instance" "primary" {

Resource aws_db_instance.primary has lifecycle.prevent_destroy set, but the
plan calls for this resource to be destroyed.

To actually remove the resource you must first delete the prevent_destroy line (or set it to false) and re-apply. Note that prevent_destroy cannot block destruction when the entire resource block is deleted and its lifecycle setting goes with it — keep the resource defined while you intend the guard to apply.

ignore_changes

Sometimes a resource attribute is legitimately modified outside of Terraform — an autoscaling group resizes itself, a deploy pipeline updates a task definition, or a tag is added by a cost-management tool. Without intervention, every plan would try to revert that drift. ignore_changes tells Terraform to stop tracking the listed attributes after creation.

resource "aws_autoscaling_group" "app" {
  name             = "app-asg"
  min_size         = 2
  max_size         = 10
  desired_capacity = 2

  lifecycle {
    ignore_changes = [desired_capacity, tag]
  }
}

Attributes are referenced by their schema names (bare identifiers, no quotes), and you can index into maps such as tags["Owner"]. To ignore drift on every attribute, use the special keyword all:

lifecycle {
  ignore_changes = all
}

Warning: ignore_changes = all is a blunt instrument. After creation, Terraform will never update the resource for any reason. Prefer an explicit list so deliberate configuration changes still apply.

replace_triggered_by

replace_triggered_by forces a resource to be replaced when another resource or attribute changes, even if the resource’s own configuration is unchanged. It accepts a list of references to managed resources, instances, or specific attributes. This is useful for rebuilding a compute resource whenever an upstream artifact changes.

resource "aws_instance" "worker" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"

  lifecycle {
    replace_triggered_by = [
      aws_launch_template.app.latest_version
    ]
  }
}

When aws_launch_template.app.latest_version changes, aws_instance.worker is destroyed and recreated. References must point to managed resources — you cannot trigger on input variables or data sources directly.

Option reference

ArgumentTypeEffect
create_before_destroyboolCreate the replacement before destroying the original (zero-downtime)
prevent_destroyboolError out on any plan that would destroy the resource
ignore_changeslist / allStop tracking drift on the listed attributes after creation
replace_triggered_bylist of referencesForce replacement when a referenced resource/attribute changes

Best Practices

  • Pair create_before_destroy with name_prefix (or other generated identifiers) so the old and new resources can coexist without naming collisions.
  • Reserve prevent_destroy for genuinely irreplaceable, stateful resources; overusing it makes routine refactors painful.
  • Prefer an explicit ignore_changes list over all so intentional config changes still take effect.
  • Use ignore_changes for attributes that are managed by autoscaling, blue/green deploy tooling, or external automation rather than disabling the integration.
  • Remember that lifecycle arguments take literal values only — they cannot reference variables, so keep them simple and self-contained.
  • Document why each lifecycle override exists with a comment; future maintainers need to know whether the drift is expected.
  • These behaviors are identical in OpenTofu, so the same patterns are portable across both tools.
Last updated June 14, 2026
Was this helpful?