Terraform Lifecycle Policies
Sometimes when deploying infrastructure the default behaviour when creating, destroying or even updating resources isn’t ideal. In order to successfully manage this you can utilize Terraform Lifecycle Policies. In this blog post we will explore various policies and how to use them in same use cases. Most common examples for using lifecycle policies:
- Prevent downtime during resource replacement
- Ignore specific changes in your resources
- Customize deletion behaviour for backups
create_before_destroy
I believe that create_before_destroy is most used Terraform lifecycle policy because we often need a way to ensure that new resource is created before old one is destroyed.
Use case example:
resource "aws_security_group" "this" {
name = var.security_group_name
description = "Security group"
vpc_id = module.context.vpc_id
egress {
description = "Allow outbound traffic in security_group"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
lifecycle {
create_before_destroy = true
}
}Used to avoid disruption when security group replacement is needed due to name hashing or attribute changes.
resource "aws_api_gateway_domain_name" "this" {
domain_name = var.api_domain_name
regional_certificate_arn = var.regional_certificate_arn
endpoint_configuration {
types = ["REGIONAL"]
}
security_policy = "TLS_1_2"
lifecycle {
create_before_destroy = true
}
}Ensures minimal downtime for API Gateway domains during updates.
resource "aws_wafv2_ip_set" "this" {
for_each = var.custom_waf_config
name = each.key
scope = "REGIONAL"
ip_address_version = "IPV4"
description = "IP set for ${each.key}"
addresses = [for ip in each.value.ips : ip]
lifecycle {
create_before_destroy = true
}
}Prevents errors in WAF during IP set changes that would otherwise leave the WAF referencing a deleted resource.
ingore_changes
Sometimes you need to tell Terraform to ignore specific attributes when detecting changes. This is especially useful when using some external process or cloud provider that might update a resource and you don’t want Terraform to overwrite it.
resource "aws_security_group" "example" {
name = "example-sg"
description = "Security group managed by Terraform"
lifecycle {
ignore_changes = [
name,
description
]
}
}This Terraform lifecycle policy will prevent Terraform from trying to revert changes made manually in AWS console for example. Without ignore_changes, Terraform would attempt to revert those updates, possibly triggering unwanted changes or replacements. You can also use this lifecycle policy when Terraform-managed and manually-managed changes need to coexist, for example if DNS record is managed both by Terraform but also UI or maybe when you expect dynamic values to shift over time outside of Terraform’s control such as autoscaling configurations.
prevent_destroy
This Terraform lifecycle policy is straightforward – it prevents the resource from being destroyed by Terraform. Basically you can prevent any resource from being accidentally destroyed but best practice is to use it for very important resources such as production database, S3 bucket with valuable data, secrets, log groups, etc.
resource "aws_s3_bucket" "critical_data" {
bucket = "my-prod-data"
lifecycle {
prevent_destroy = true
}
}Backup Lifecycle Management (cold_storage_after / delete_after)
This is often used with services like AWS Backup or similar.
resource "aws_backup_plan_rule" "example" {
rule_name = "daily-backup"
target_vault_name = aws_backup_vault.main.name
schedule = "cron(0 12 * * ? *)"
lifecycle {
delete_after = var.delete_backup_after # e.g., 35 days
cold_storage_after = var.cold_storage_after # e.g., 7 days
}
}This helps manage backup costs by moving old backups to cheaper cold storage and eventually deleting them.
Combining Multiple Terraform Lifecycle Policies
You can mix and match lifecycle rules as needed:
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = "t3.micro"
lifecycle {
create_before_destroy = true
ignore_changes = ["user_data"]
prevent_destroy = true
}
}This setup is helpful when you’re deploying a critical EC2 instance where user data might be updated manually or outside of Terraform. First you would ensure that when Terraform needs to replace EC2 instance due to an AMI change or whatever, it will create the new one before destroying the old one and therefore reducing downtime.
Since we used ignore_changes for user_data, Terraform will not try to update instance if only the user_data has changed. We can say that this policy is not really needed but might be useful if user_data is injected or modified by external automation and doesn’t need to trigger resource replacement.
Of course we can add additional safety by setting prevent_destroy to true to block accidental deletion of this EC2 instance.
Conclusion
Terraform lifecycle policies are powerful tools that allow fine-grained control over how infrastructure is created, modified, and destroyed. Used wisely, they can prevent costly downtime, preserve critical data, and avoid unintended configuration drifts.
If this blog saved you time, support me with a coffee!
Thanks to everyone who’s supported!







