Why lifecycle exists
By default Terraform follows a simple rule: if HCL changed, update the resource; if it was removed from HCL, delete the resource. This works in 90% of cases, but sometimes you need to adjust that behavior.
lifecycle is a nested block inside resource that gives you four options for fine-grained control.
prevent_destroy = true, guarding against accidents
This is the most valuable option for production:
resource "aws_db_instance" "main" {identifier = "prod-db"
# ... Parameters ...
lifecycle {prevent_destroy = true
}
}
Any attempt to delete this resource via apply or destroy now fails with an error:
Error: Instance cannot be destroyed
Resource aws_db_instance.main has lifecycle.prevent_destroy set, but the
plan calls for this resource to be destroyed.
To actually delete the resource, you must remove prevent_destroy = true, run apply, and only then attempt destroy again. Two-step protection. You have to consciously disable the flag first.
Use it on: prod databases, S3 buckets with data, KMS keys, and any resource whose loss would be catastrophic.
create_before_destroy = true, zero-downtime
By default, when a resource must be replaced (-/+), Terraform does:
- Delete the old one.
- Create the new one.
There is downtime between steps 1 and 2. For critical resources that is unacceptable.
resource "aws_launch_template" "web" {name_prefix = "web-"
instance_type = "t3.micro"
lifecycle {create_before_destroy = true
}
}
With this flag set, Terraform:
- Creates the new resource.
- Switches dependencies to the new one.
- Deletes the old one.
Caveats:
- The resource must have a unique name. You cannot use
name = "web"because there will be a conflict (the old one is not deleted yet, so a new one with the same name cannot be created). Usename_prefixor include a suffix in the name. - Some resources have constraints. AWS RDS does not allow two instances with the same identifier.
ignore_changes = [...], ignoring drift
Sometimes an attribute changes outside Terraform and you do not care. A last_modified tag set by an external script. Auto-scaling changing desired_capacity. Without ignore_changes, every plan would show a diff and propose a rollback.
resource "aws_autoscaling_group" "web" {name = "web-asg"
min_size = 2
max_size = 10
desired_capacity = 2
lifecycle {# The ASG controller changes desired_capacity automatically; leave it alone.
ignore_changes = [desired_capacity]
}
}
You can ignore everything:
lifecycle {ignore_changes = all
}
That is rarely correct. It turns the resource into "created once, no longer managed."
replace_triggered_by = [...], forcing replacement on a signal
Sometimes you need to recreate a resource when a different resource changes. For example, an EC2 instance should be replaced when user_data changes, even if the AWS provider does not consider that a reason for replacement.
resource "null_resource" "config_version" { triggers = { config_hash = filemd5("config.yaml")}
}
resource "aws_instance" "web" {ami = "ami-..."
instance_type = "t3.micro"
lifecycle {replace_triggered_by = [null_resource.config_version]
}
}
When config.yaml changes, null_resource.config_version is replaced (new triggers), and aws_instance.web is replaced along with it.
Pitfalls
-
prevent_destroydoes not protect againstterraform state rm. If someone removes the resource from state by hand, Terraform no longer tracks it, andprevent_destroyhas no effect. It only blocks changes made through apply/destroy. -
create_before_destroyrequires unique naming. The name, identifier, or any attribute with a unique constraint must be dynamic. Otherwise you get a conflict. -
ignore_changesdoes not apply to add/remove. If you add a tag in HCL that did not exist before,ignore_changes = [tags]will not stop Terraform from creating it. The ignore only applies when the cloud and HCL diverge, not when you explicitly change HCL. -
lifecycle is not available in a data block. It is a construct for resource blocks only.
-
One lifecycle block per resource. Multiple lifecycle blocks in a single resource is an error.
-
Do not use
ignore_changesto hide a problem. If plan shows unexpected drift, find out why the attribute is changing outside Terraform. Fix the cause, not the symptom.