Skip to main content
Terraform Lifecycle Rules: prevent_destroy, create_before_destroy, ignore_changes

Terraform Lifecycle Rules: prevent_destroy, create_before_destroy, ignore_changes

Key Takeaway

Complete guide to Terraform lifecycle rules. Learn prevent_destroy, create_before_destroy, ignore_changes

Table of Contents

Introduction

Terraform lifecycle rules control what happens when a resource needs to be created, updated, or destroyed. They’re essential for production infrastructure — preventing accidental deletion, enabling zero-downtime deployments, and handling resources that are modified outside of Terraform.

Quick Reference

resource "aws_instance" "web" {
  # ... resource config ...

  lifecycle {
    create_before_destroy = true       # Create new before destroying old
    prevent_destroy       = true       # Block terraform destroy
    ignore_changes        = [tags]     # Ignore external changes
    replace_triggered_by  = [null_resource.trigger.id]  # Force replacement
  }
}

All Four Lifecycle Rules

1. prevent_destroy — Protect Critical Resources

prevent_destroy = true makes Terraform refuse to destroy a resource. Any terraform destroy or plan that would destroy the resource produces an error instead.

resource "aws_db_instance" "production" {
  identifier     = "prod-database"
  engine         = "postgresql"
  instance_class = "db.r6g.large"
  allocated_storage = 100

  lifecycle {
    prevent_destroy = true
  }
}

If you try to destroy it:

Error: Instance cannot be destroyed

  on main.tf line 1:
   1: resource "aws_db_instance" "production" {

Resource aws_db_instance.production has lifecycle.prevent_destroy set,
but the plan calls for this resource to be destroyed.

Use for:

  • Production databases
  • S3 buckets with important data
  • KMS encryption keys
  • IAM roles used by multiple services
  • DNS zones

To actually destroy it: Set prevent_destroy = false first, then run terraform destroy.

2. create_before_destroy — Zero-Downtime Updates

When Terraform needs to replace a resource (destroy + create), it normally destroys first, then creates. create_before_destroy = true reverses this order: create the new resource first, verify it works, then destroy the old one.

resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = "t3.micro"

  lifecycle {
    create_before_destroy = true
  }
}

Timeline without create_before_destroy:

1. Destroy old instance     → DOWNTIME STARTS
2. Create new instance      → DOWNTIME ENDS

Timeline with create_before_destroy:

1. Create new instance      → No downtime
2. Verify new instance
3. Destroy old instance     → Seamless cutover

Common use cases:

Auto Scaling Groups:

resource "aws_launch_template" "app" {
  name_prefix   = "app-"
  image_id      = var.ami_id
  instance_type = "t3.micro"

  lifecycle {
    create_before_destroy = true
  }
}

Security Groups (referenced by instances):

resource "aws_security_group" "web" {
  name_prefix = "web-"
  vpc_id      = aws_vpc.main.id

  lifecycle {
    create_before_destroy = true
  }
}

Watch out for naming conflicts: If a resource has a unique name (like an S3 bucket or IAM role), creating a new one before destroying the old one will fail with a “name already exists” error. Use name_prefix instead of name to avoid this.

3. ignore_changes — Handle External Modifications

ignore_changes tells Terraform to ignore changes to specific attributes. If something modifies the resource outside of Terraform (AWS Console, another tool, auto-scaling), Terraform won’t try to revert it.

resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t3.micro"

  tags = {
    Name = "web-server"
  }

  lifecycle {
    ignore_changes = [tags, ami]
  }
}

Now if someone adds tags in the AWS Console or the AMI changes, terraform plan won’t show a diff.

Ignore all attributes:

lifecycle {
  ignore_changes = all
}

This makes Terraform manage only the initial creation — after that, it ignores all changes. Useful for resources you want Terraform to create but not manage day-to-day.

Common use cases:

Auto-scaled instance counts:

resource "aws_autoscaling_group" "app" {
  min_size         = 2
  max_size         = 10
  desired_capacity = 2

  lifecycle {
    ignore_changes = [desired_capacity]
  }
}

ECS services with auto-scaling:

resource "aws_ecs_service" "app" {
  name            = "app"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.app.arn
  desired_count   = 2

  lifecycle {
    ignore_changes = [desired_count, task_definition]
  }
}

Tags managed by AWS (auto-tagging, Service Catalog):

resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = "t3.micro"

  lifecycle {
    ignore_changes = [tags["aws:autoscaling:groupName"], tags["kubernetes.io/cluster"]]
  }
}

4. replace_triggered_by — Force Replacement on External Change

Added in Terraform 1.2. Forces a resource to be replaced when another resource or attribute changes:

resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = "t3.micro"

  lifecycle {
    replace_triggered_by = [
      aws_security_group.web.id,
      null_resource.force_replace.id
    ]
  }
}

resource "null_resource" "force_replace" {
  triggers = {
    deploy_version = var.deploy_version
  }
}

When var.deploy_version changes, null_resource.force_replace is recreated, which triggers replacement of aws_instance.web.

Use cases:

  • Force EC2 replacement when security group changes
  • Trigger redeployment based on a version variable
  • Chain replacements across dependent resources

Combining Lifecycle Rules

You can use multiple rules together:

# Production database: protected, ignores external tag changes
resource "aws_db_instance" "production" {
  identifier     = "prod-database"
  engine         = "postgresql"
  instance_class = "db.r6g.large"

  lifecycle {
    prevent_destroy = true
    ignore_changes  = [tags, engine_version]
  }
}

# Web server: zero-downtime replacement, ignore auto-scaling tags
resource "aws_launch_template" "web" {
  name_prefix   = "web-"
  image_id      = var.ami_id
  instance_type = "t3.micro"

  lifecycle {
    create_before_destroy = true
    ignore_changes        = [tags]
  }
}

Production Patterns

Pattern 1: Protected Database with Managed Backups

resource "aws_db_instance" "main" {
  identifier                = "production-db"
  engine                    = "postgresql"
  engine_version            = "15.4"
  instance_class            = "db.r6g.large"
  allocated_storage         = 100
  backup_retention_period   = 7
  deletion_protection       = true  # AWS-level protection

  lifecycle {
    prevent_destroy = true           # Terraform-level protection
    ignore_changes  = [engine_version]  # Allow RDS auto minor upgrades
  }
}

Pattern 2: Blue-Green Deployment

resource "aws_lb_target_group" "app" {
  name_prefix = "app-"
  port        = 80
  protocol    = "HTTP"
  vpc_id      = aws_vpc.main.id

  health_check {
    path = "/health"
  }

  lifecycle {
    create_before_destroy = true
  }
}

Pattern 3: Kubernetes-Managed Resources

resource "aws_eks_node_group" "workers" {
  cluster_name    = aws_eks_cluster.main.name
  node_group_name = "workers"
  instance_types  = ["t3.medium"]

  scaling_config {
    desired_size = 3
    min_size     = 2
    max_size     = 10
  }

  lifecycle {
    ignore_changes = [scaling_config[0].desired_size]
  }
}

Common Errors

“Instance cannot be destroyed”

You set prevent_destroy = true but are trying to remove the resource. Options:

  1. Set prevent_destroy = false, apply, then remove
  2. Use terraform state rm to remove from state without destroying

“create_before_destroy” naming conflict

Error: A resource with the ID "my-security-group" already exists

Use name_prefix instead of name:

resource "aws_security_group" "web" {
  name_prefix = "web-"  # Not name = "web-sg"

  lifecycle {
    create_before_destroy = true
  }
}

ignore_changes not working with maps

For map/object attributes, reference the whole attribute or specific keys:

# Ignore all tags
ignore_changes = [tags]

# Ignore specific tag key
ignore_changes = [tags["ManagedBy"]]

Hands-On Courses

Learn by doing with interactive courses on CopyPasteLearn:

Conclusion

Lifecycle rules are essential for production Terraform. Use prevent_destroy to protect databases and critical resources, create_before_destroy for zero-downtime deployments, ignore_changes to coexist with auto-scaling and external tools, and replace_triggered_by to chain resource replacements. Most production resources should have at least one lifecycle rule configured.

🚀

Level Up Your Terraform Skills

Hands-on courses, books, and resources from Luca Berton

Luca Berton
Written by

Luca Berton

DevOps Engineer, AWS Partner, Terraform expert, and author. Creator of Ansible Pilot, Terraform Pilot, and CopyPasteLearn.