
The lifecycle block changes how Terraform handles a resource, overriding its normal behavior. One of its arguments, ignore_changes, helps when something outside your Terraform code modifies a resource and you don't want Terraform to undo that change on the next run.
Put simply, ignore_changes tells Terraform: "When you run a plan, don't consider changes to these specific attributes, even if the state value no longer matches the HCL value."
ignore_changes Solve?Terraform's job is to keep the actual state of your infrastructure matching the desired state you defined in your HCL code. When the two don't match, Terraform plans an update to correct the drift.
ignore_changes earns its keep in three common cases where that drift is harmless or intentional:
ignore_changes, Terraform keeps trying to remove or reset the attribute, which clutters your plan.ignore_changes can temporarily suppress false positives so you can update the HCL gradually without risking an immediate, destructive apply.ignore_changes BlockThe lifecycle block is placed inside a resource block. The ignore_changes argument accepts a list of attribute names that should be excluded from drift detection.
resource "resource_type" "resource_name" {
# Standard resource configuration...
lifecycle {
# Provide a list of attribute names to ignore.
ignore_changes = [
attribute_one,
attribute_two,
# You can also use the special keyword 'all'
# all,
]
}
}
Say you have an AWS EC2 instance. You want Terraform to manage its type and AMI, but you let your monitoring team update certain tags, and a separate tool manages the user data script once the instance is running.
resource "aws_instance" "web_server" {
ami = "ami-0abcdef1234567890"
instance_type = "t2.micro"
# Standard tags managed by Terraform
tags = {
Project = "MyWebApp"
Owner = "Team Alpha"
}
lifecycle {
# Ignore tags_all, allowing external automation to add/remove tags without
# triggering a plan difference for this resource.
# Also ignore 'user_data' if a bootstrap tool modifies it post-launch.
ignore_changes = [
tags_all,
user_data,
]
}
}
tags vs. tags_all DilemmaMost cloud providers have a tags argument (explicitly managed) and a tags_all argument (the complete set of tags, including provider-default tags).
ignore_changes = [tags], Terraform will ignore only the tags you explicitly defined. This is rarely what you want.ignore_changes = [tags_all], Terraform ignores all tag changes on the resource. This is often the safest choice if you want to allow external tag management, but it means Terraform can never enforce a specific tag set.allThe special keyword all ignores changes to every attribute on the resource.
lifecycle {
ignore_changes = all
}
This stops all drift, but it also makes the resource read-only as far as Terraform is concerned. Any change you make to that resource's block in HCL (say, changing instance_type from t2.micro to t2.medium) gets ignored. Use all only when another system manages the resource entirely and you just need Terraform to keep it from being destroyed by accident.
ignore_changes works by comparing the current state (what Terraform read from the cloud) against the configuration (what is written in your HCL). It only suppresses the plan output; the actual change still exists in the cloud.
If you change an attribute in your HCL but also have ignore_changes set for it, Terraform ignores your HCL change on the next apply. To make the change stick, remove the attribute from the ignore_changes list, run apply, and then add it back if you want.
for_each and Dynamic AttributesYou can use a for loop inside the ignore_changes block to dynamically generate a list of attributes to ignore, but only when ignoring attributes on a set of resources created with count or for_each.
For example, to ignore the description on a set of security group rules:
resource "aws_security_group_rule" "ingress" {
# ... configuration using count or for_each
lifecycle {
ignore_changes = [
for i in range(0, length(aws_security_group_rule.ingress)) : ingress[i].description
]
}
}
ignore_changes with DriftThe ignore_changes lifecycle meta-argument is your main tool for controlling configuration drift on specific attributes of a resource. Drift happens when the actual state of a resource in the cloud no longer matches the desired configuration in your HCL code.
When you set ignore_changes, you're telling Terraform to stop tracking a specific set of attributes on a resource, so it won't plan the updates it would otherwise generate to correct the drift.
Keep in mind that ignore_changes only suppresses drift on the attributes you call out ahead of time. Detecting and remediating unexpected drift across hundreds of resources and workspaces is a separate problem, and one of the main reasons teams adopt a dedicated Terraform automation platform with continuous drift detection built in.
For example, say you manage an EC2 instance but want some other system to update its tags and user data without Terraform reverting those changes on every run.
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = "t2.micro"
# Standard configuration that Terraform WILL manage
subnet_id = aws_subnet.public.id
lifecycle {
# 1. Ignore the 'user_data' field, allowing a separate script to modify it.
# 2. Ignore 'tags_all', which prevents Terraform from trying to remove
# any tags added by non-Terraform systems.
ignore_changes = [
user_data,
tags_all,
]
}
}
Most ignore_changes blocks exist to tolerate out-of-band edits, and tolerated drift is still drift. If the list of ignored attributes keeps growing, schedule drift detection rather than widening it further: our drift detection guide covers the workflow, and Scalr runs scheduled drift checks across workspaces, free up to 50 runs per month, with drift-detection runs not counted against that allowance.
This blog has been verified for Terraform and OpenTofu
