Terraform usually does the right thing when it comes to creating, updating, and destroying infrastructure. But “usually” isn’t always good enough—especially when downtime, data loss, or external changes are involved.
This is where Terraform lifecycle meta-arguments come in. They let you tweak how Terraform behaves during resource changes without rewriting your entire configuration.
Let’s break this down with practical scenarios and working examples.
What is the Terraform lifecycle block?
The lifecycle block is a meta-argument that can be added inside any resource. It gives you fine-grained control over how Terraform handles updates and deletions.
Here’s a minimal example:
1resource "aws_instance" "example" {
2 ami = "ami-123456"
3 instance_type = "t3.micro"
4
5 lifecycle {
6 create_before_destroy = true
7 }
8}
9Instead of blindly replacing resources, Terraform now follows rules you define.
create_before_destroy: avoiding downtime
By default, Terraform destroys a resource and then creates a new one. That’s risky for production systems.
create_before_destroy = true flips that behavior.
Example: updating an AWS EC2 instance that requires replacement.
1resource "aws_instance" "web" {
2 ami = "ami-newversion"
3 instance_type = "t3.small"
4
5 lifecycle {
6 create_before_destroy = true
7 }
8}
9What happens:
- Terraform creates the new instance first
- Then destroys the old one
This is critical for:
- Load-balanced services
- Zero-downtime deployments
- Blue/green-style updates
Gotcha: Some resources can’t exist twice (e.g., same name constraints). You may need unique naming strategies.
prevent_destroy: your safety net
Accidentally destroying a database is the kind of mistake you only make once.
prevent_destroy = true blocks Terraform from deleting a resource—even if the plan says it should.
1resource "aws_db_instance" "prod_db" {
2 identifier = "production-db"
3 engine = "postgres"
4
5 lifecycle {
6 prevent_destroy = true
7 }
8}
9If someone runs terraform destroy or changes something that requires replacement, Terraform will throw an error.
This setting is especially useful for databases, storage buckets, and anything holding critical data.
Important detail: To actually destroy the resource, you must remove or modify this lifecycle rule first.
ignore_changes: when Terraform should “look away”
Sometimes infrastructure changes outside Terraform—either manually or via another system.
Without intervention, Terraform will try to “fix” those changes on the next apply.
ignore_changes tells Terraform to leave certain attributes alone.
1resource "aws_instance" "app" {
2 ami = "ami-123456"
3 instance_type = "t3.micro"
4
5 tags = {
6 Environment = "production"
7 }
8
9 lifecycle {
10 ignore_changes = [tags]
11 }
12}
13Use cases:
- External tagging systems (e.g., cost allocation tools)
- Auto-scaling group modifications
- Manual hotfixes during incidents
Granular control:
1lifecycle {
2 ignore_changes = [
3 tags["Owner"],
4 instance_type
5 ]
6}
7This avoids blanket ignoring and keeps your infrastructure predictable.
A quick real-world scenario
Imagine you’re managing a production API service:
- EC2 instances behind a load balancer
- Auto-scaling enabled
- External system modifies tags
A practical lifecycle configuration might look like this:
1resource "aws_launch_template" "api" {
2 name_prefix = "api-template"
3 image_id = "ami-xyz"
4 instance_type = "t3.medium"
5
6 lifecycle {
7 create_before_destroy = true
8 ignore_changes = [tags]
9 }
10}
11And for the database:
1resource "aws_db_instance" "api_db" {
2 identifier = "api-db"
3
4 lifecycle {
5 prevent_destroy = true
6 }
7}
8This combination ensures:
- No downtime during updates
- No accidental database deletion
- No unnecessary drift corrections
Common mistakes developers make
Lifecycle rules are powerful—but easy to misuse.
Overusing ignore_changes
If you ignore too much, Terraform loses control of your infrastructure state. Drift becomes invisible.
Forgetting prevent_destroy in critical systems
This often shows up after a near-miss or incident. It’s better to add it proactively.
Using create_before_destroy without dependencies
If dependent resources don’t handle duplication well, you can run into conflicts or quota issues.
When not to use lifecycle rules
It’s tempting to solve every issue with lifecycle tweaks, but sometimes the real fix is better design.
Avoid lifecycle when:
- You’re masking poor module structure
- You’re ignoring critical configuration drift
- You haven’t fully understood why Terraform wants to replace a resource
Lifecycle is a scalpel—not a hammer.
Why lifecycle matters in modern DevOps
Infrastructure is no longer static. It’s dynamic, shared, and often modified by multiple systems.
The Terraform lifecycle block helps you:
- Align infrastructure behavior with real-world constraints
- Prevent outages during deployments
- Protect critical resources
- Coexist with external systems
Used thoughtfully, it turns Terraform from a rigid tool into something far more adaptable.
And in production environments, that flexibility is often the difference between a smooth rollout and a late-night incident.