Terraform Lifecycle Meta-Arguments
The lifecycle block lets you customize how Terraform creates, updates, and destroys resources. It overrides the default behavior for specific resources — essential for zero-downtime deployments, protecting critical resources, and managing drift in resources that change outside Terraform.
create_before_destroy
By default, Terraform destroys the old resource before creating the new one. create_before_destroy reverses this — new resource first, then destroy the old. Critical for any resource that supports load — swapping an old server for a new one without downtime.
resource "aws_instance" "web" { ami = data.aws_ami.ubuntu.id instance_type = var.instance_type
lifecycle { create_before_destroy = true }}
resource "aws_lb_listener_certificate" "api" { listener_arn = aws_lb_listener.https.arn certificate_arn = aws_acm_certificate.api.arn
lifecycle { create_before_destroy = true # Add new cert before removing old one }}
# ACM certificates themselves need create_before_destroyresource "aws_acm_certificate" "api" { domain_name = "api.example.com" validation_method = "DNS"
lifecycle { create_before_destroy = true # Required for certificate rotation }}prevent_destroy
Blocks terraform destroy and any plan that would destroy this resource. Use for databases, production state buckets, and other resources where accidental deletion is catastrophic.
resource "aws_db_instance" "production" { identifier = "production-postgres" instance_class = "db.r6g.large" engine = "postgres"
lifecycle { prevent_destroy = true # Any plan that would destroy this will fail }}
resource "aws_s3_bucket" "terraform_state" { bucket = "mycompany-terraform-state"
lifecycle { prevent_destroy = true }}When Terraform tries to destroy a prevent_destroy resource:
Error: Instance cannot be destroyed
on main.tf line 5, in resource "aws_db_instance" "production": 5: lifecycle { 6: prevent_destroy = true 7: }
This object is protected from destroy action and cannot be destroyed.ignore_changes
Tell Terraform to ignore changes to specific attributes — Terraform won’t try to restore them to the config value after an external change:
resource "aws_autoscaling_group" "app" { name = "app-asg" min_size = 2 max_size = 20 desired_capacity = 3 # Auto-scaling changes this — Terraform should ignore it
lifecycle { ignore_changes = [desired_capacity] # Don't override auto-scaling decisions }}
resource "aws_ecs_service" "api" { name = "api" task_definition = aws_ecs_task_definition.api.arn desired_count = 2
lifecycle { ignore_changes = [ desired_count, # Auto-scaling manages this task_definition, # Deployment pipeline updates this separately ] }}
# Ignore all changes to tags (common for resources with externally-managed tags)resource "aws_instance" "web" { ami = data.aws_ami.ubuntu.id instance_type = "t3.micro"
lifecycle { ignore_changes = [tags] # Tag management tool controls these }}
# Ignore all changes (not recommended — use sparingly)resource "aws_instance" "imported_legacy" { lifecycle { ignore_changes = [all] # Terraform tracks but never modifies this resource }}replace_triggered_by
Force replacement of a resource when another resource or attribute changes (Terraform 1.2+):
resource "aws_instance" "web" { ami = data.aws_ami.ubuntu.id instance_type = "t3.micro"
lifecycle { # Replace this instance when the launch template changes replace_triggered_by = [ aws_launch_template.app.latest_version ] }}
resource "aws_apprunner_service" "api" { service_name = "api"
lifecycle { # Replace the App Runner service when the source image changes replace_triggered_by = [ null_resource.image_push # A null_resource that triggers on image changes ] }}precondition and postcondition (Terraform 1.2+)
Validate assumptions about the environment before and after apply:
resource "aws_instance" "web" { ami = data.aws_ami.ubuntu.id instance_type = var.instance_type
lifecycle { precondition { condition = contains(["t3.micro", "t3.small", "t3.medium"], var.instance_type) error_message = "instance_type must be t3.micro, t3.small, or t3.medium." }
precondition { condition = data.aws_ami.ubuntu.architecture == "x86_64" error_message = "AMI must be x86_64 architecture." }
postcondition { condition = self.public_ip != "" error_message = "Instance must have a public IP — check subnet settings." } }}
# Cross-resource validationresource "aws_db_instance" "main" { instance_class = var.db_instance_class
lifecycle { precondition { condition = !(var.environment == "production" && var.db_instance_class == "db.t3.micro") error_message = "Production databases must not use db.t3.micro. Use db.r6g.large or larger." } }}Combining lifecycle Arguments
Multiple lifecycle arguments work together:
resource "aws_acm_certificate" "main" { domain_name = var.domain_name validation_method = "DNS"
lifecycle { create_before_destroy = true # Safe certificate rotation prevent_destroy = true # Don't accidentally delete a valid cert
# If the cert is re-issued by an external process, don't fight it ignore_changes = [subject_alternative_names] }}