Zero-downtime replacements and protection of critical resources come from lifecycle rules.
Terraform orders operations from references. When a dependency is real but not visible through a reference (an app needs an IAM policy to be attached before it starts, but never uses the policy's attributes), add depends_on = [aws_iam_role_policy.app]. Use it sparingly: it makes plans more conservative, because more values become unknown.
The lifecycle block changes how changes are carried out:
create_before_destroy = true: when replacement is needed, create the new object first, then destroy the old one. Essential for things in use, such as certificates or launch templates. Names must not collide, so usename_prefixor generated names.prevent_destroy = true: any plan that would destroy this resource fails. A guard for databases and state buckets. It doesn't help if the block itself is deleted from the code.ignore_changes = [tags, desired_count]: don't fight changes made outside Terraform to these attributes (an autoscaler changing the count).replace_triggered_by = [terraform_data.version]: replace this resource when another changes.preconditionandpostconditionblocks check assumptions (next level).
terraform_data (built in, no provider needed) is a handy placeholder resource for triggers, replacing the old null_resource.
Example
resource "aws_launch_template" "app" {
name_prefix = "app-" # unique names allow overlap
image_id = var.ami_id
instance_type = "t3.small"
lifecycle { create_before_destroy = true }
}
resource "aws_db_instance" "main" {
identifier = "shop-prod"
engine = "postgres"
instance_class = "db.t4g.medium"
allocated_storage = 50
lifecycle {
prevent_destroy = true
ignore_changes = [allocated_storage] # storage autoscaling grows it
}
}
resource "aws_ecs_service" "app" {
name = "app"
depends_on = [aws_iam_role_policy.app] # hidden ordering requirement
# ... task_definition, cluster, desired_count ...
}Remember
- depends_on only for dependencies not expressed by references
- create_before_destroy for in-use objects; needs non-colliding names
- prevent_destroy guards; ignore_changes stops fights with external changes
Watch out for
- Relying on prevent_destroy while deleting the whole resource block: the guard goes with it.