depends_on and the lifecycle block

Terraform · Practical · 10 min

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 use name_prefix or 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.
  • precondition and postcondition blocks 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.

Check yourself

  1. An autoscaler changes desired_count and every apply resets it. Best fix?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: developer.hashicorp.com/terraform/language/meta-arguments/lifecycle