Terraform lifecycle: prevent_destroy, create_before_destroy, ignore_changes
Control how Terraform creates, updates and destroys a resource #
The lifecycle block is available in every resource and changes the default behavior of the plan. For a different approach to a related problem, see avoiding instance destroy in AWS.
prevent_destroy #
Makes any plan that would destroy the resource fail. Use it for databases, buckets with data and anything hard to recover:
resource "aws_db_instance" "main" {
identifier = "ditwl-pro-db"
# ...
lifecycle {
prevent_destroy = true
}
}It does not protect against removing the resource and its lifecycle block from the code at the same time, so it is a safety net and not a security control. Use AWS deletion protection too.
create_before_destroy #
When a change forces replacement, Terraform destroys first and then creates. With create_before_destroy the new object is created first, so there is no gap:
resource "aws_launch_template" "web" {
name_prefix = "web-"
image_id = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
lifecycle {
create_before_destroy = true
}
}Both objects exist for a moment, so unique names must not collide. Use name_prefix instead of name, as in the example. It is common in auto scaling groups and load balancers.
ignore_changes #
Tells Terraform not to react to differences in some attributes. It is useful when another system changes them, for example autoscaling changing desired_capacity:
resource "aws_autoscaling_group" "web" {
# ...
desired_capacity = 2
lifecycle {
ignore_changes = [desired_capacity]
}
}Use ignore_changes = all only as a last resort, because Terraform will stop managing the resource.
replace_triggered_by #
Forces the replacement of a resource when another resource or attribute changes:
resource "aws_instance" "app" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
lifecycle {
replace_triggered_by = [aws_security_group.app.id]
}
}precondition and postcondition #
Custom checks that fail the plan or apply with your own message:
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
}
}
resource "aws_instance" "app" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
lifecycle {
precondition {
condition = data.aws_ami.ubuntu.architecture == "x86_64"
error_message = "The AMI must be x86_64."
}
postcondition {
condition = self.public_ip != ""
error_message = "The instance must have a public IP."
}
}
}A precondition is evaluated before creating the resource and a postcondition after, with self pointing to the result.
Summary #
| Argument | Use it for |
|---|---|
prevent_destroy |
Protect critical resources from accidental destruction |
create_before_destroy |
Replace without downtime |
ignore_changes |
Attributes changed outside of Terraform |
replace_triggered_by |
Replace a resource when a dependency changes |
precondition / postcondition |
Validate assumptions with clear errors |
lifecycle arguments must be literal values: they cannot use variables or expressions. Related: import, moved and removed.