The Terraform resource lifecycle is the sequence every resource moves through: create, update, and destroy. Terraform manages that flow by default, but the defaults don’t always match what production needs. A rename that forces a destroy-and-recreate can take down a database. A tag applied by an external policy engine can trigger an endless diff.
The Terraform lifecycle meta-argument is how you override those defaults and control when resources get created, replaced, or protected.
In this article, we’ll walk through the default resource lifecycle, then each lifecycle argument, with examples of when to reach for each one.
What is a Terraform resource?
A Terraform resource block defines a piece of infrastructure with the given settings. When the resource block is defined in code, the resource object does not actually exist until terraform apply is executed. Applying a configuration can result in the creation, modification, or destruction of a resource, depending on the configuration and state of the infrastructure. Terraform will ensure the actual infrastructure matches the configured settings for the resource.
Terraform resource lifecycle
Once an object is created, it is saved in the Terraform state. Terraform can then update the object if its settings are changed in the configuration or destroy it if the resource is removed from the configuration.
Depending on the settings defined in the configuration, Terraform will take one of the following actions when applying the configuration:
Create
Creates the object with the defined settings.
Destroy
Destroys the object when the configuration no longer exists.
Update-in-place
Updates the object accordingly when the settings in the resource block are changed. For example, adding a disk to a VM in Azure can be created and added without destroying the VM first.
Destroy and recreate
Destroys the object and recreates it when a changed setting cannot be updated in place on the target platform. For example, you cannot rename a VM in Azure without replacing it, so Terraform destroys the old VM and creates a new one with the name specified in the resource block.
Terraform state can contain very sensitive data. Sometimes this is unavoidable because of the design of certain Terraform providers or because the definition of what is sensitive isn’t always simple and may vary between individuals and organizations. Spacelift provides two different approaches for sanitizing values when resources are stored or passed to Plan policies:
- Default Sanitization: All string values are sanitized.
- Smart Sanitization: Only the values marked as sensitive are sanitized.
Learn more about how Spacelift can help you with Resource Sanitization, and get started on your journey by creating a free trial account.
What is Terraform lifecycle meta-argument?
The Terraform lifecycle is a nested configuration block within a resource block. The lifecycle meta-argument can be used to specify how Terraform should handle the creation, modification, and destruction of resources. Meta-arguments are arguments used in resource blocks.
Terraform lifecycle meta-argument example
The lifecycle meta-argument can appear inside any resource block and lets you customize how Terraform creates, updates, and destroys that resource. For example, you can tell Terraform to ignore tag changes that are managed by an external policy engine:
resource "azurerm_resource_group" "example_rg" {
name = "example-rg"
location = "westeurope"
lifecycle {
ignore_changes = [
tags["department"]
]
}
}Managing the resource lifecycle using the lifecycle meta-argument
Controlling the flow of Terraform operations can be achieved using the lifecycle meta-argument. This is useful in scenarios when you need to protect items from getting changed or destroyed.
A common scenario that requires the use of a lifecycle meta-argument occurs when the Terraform provider itself does not handle a change correctly and can be safely ignored, rather than the provider necessarily attempting to update an object. With the provider version updates, these “bugs” are slowly ironed out, at which point the lifecycle meta-argument can be removed from the resource.
There are several attributes available for use with the lifecycle meta-argument:
| Argument | What it does | When to use it |
create_before_destroy |
Creates the replacement before destroying the original | Avoid downtime on resources that must be replaced |
prevent_destroy |
Blocks any plan that would destroy the resource | Guard stateful or production-critical resources |
ignore_changes |
Ignores changes to listed attributes | An external system manages a field (tags, scaling, image) |
replace_triggered_by |
Replaces the resource when a referenced resource changes | A resource must be rebuilt when a dependency’s identity changes |
precondition / postcondition |
Asserts conditions before and after apply | Fail fast on bad inputs or unexpected results |
action_trigger |
Invokes a provider action on a lifecycle event | Run a side effect (Lambda, webhook) on create or update |
Let’s look at them in more detail.
create_before_destroy
When Terraform determines it needs to destroy an object and recreate it, the normal behavior will create the new object after the existing one is destroyed. Using this attribute will create the new object first and then destroy the old one. This can help reduce downtime. Some objects have restrictions that the use of this setting may cause issues with, preventing objects from existing concurrently. Hence, it is important to understand any resource constraints before using this option.
lifecycle {
create_before_destroy = true
}prevent_destroy
This lifecycle option prevents Terraform from accidentally removing critical resources. This is useful to avoid downtime when a change would result in the destruction and recreation of resource. This block should be used only when necessary as it will make certain configuration changes impossible.
lifecycle {
prevent_destroy = true
}Terraform will error when it attempts to destroy a resource when this is set to true:
Error: Instance cannot be destroyed
resource details...
Resource [resource_name] has lifecycle.prevent_destroy set, but the plan calls for this resource to be destroyed. To avoid this error and continue with the plan, either disable lifecycle.prevent_destroy or reduce the scope of the plan using the -target flag.create_before_destroy and prevent_destroy must be set to literal true or false. You cannot drive them from a variable or expression, because Terraform evaluates lifecycle rules before it evaluates arbitrary expressions.
One important limitation: prevent_destroy only stops a plan that would destroy the resource while its block still exists in your configuration. If you delete the resource block entirely, Terraform will destroy the resource anyway, because it does not record lifecycle rules in state (the one exception is create_before_destroy). Treat prevent_destroy as a guardrail against accidental replacement, not as protection against removing the resource from code.
Read more: Terraform prevent_destroy: How to Safeguard Resources
ignore_changes
The Terraform ignore_changes lifecycle option can be useful when attributes of a resource are updated outside of Terraform.
It can be used, for example, when an Azure Policy automatically applies tags. When Terraform detects the changes the Azure Policy has applied, it will ignore them and not attempt to modify the tag. Specify the attributes you want Terraform to ignore.
In the example below, the department tag will be ignored:
lifecycle {
ignore_changes = [
tags["department"]
]
}If all attributes are to be ignored, then the all keyword can be used. This means that Terraform will never update the object but will be able to create or destroy it.
lifecycle {
ignore_changes = [
all
]
}replace_triggered_by
The replace_triggered_by argument forces Terraform to replace a resource whenever one or more other managed resources change. This is useful when a resource depends on another resource’s identity in a way that requires a full replacement instead of an in-place update.
You can only reference managed resources or their attributes in replace_triggered_by. When Terraform plans an update or replacement for any of those references, it will also plan a replacement for the resource that declares replace_triggered_by.
For example, you might want an auto scaling target to be recreated whenever the ECS service it scales is replaced:
resource "aws_ecs_service" "svc" {
name = "example-svc"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.main.arn
desired_count = 2
# ...
}
resource "aws_appautoscaling_target" "ecs_target" {
max_capacity = 10
min_capacity = 2
resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.svc.name}"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
lifecycle {
replace_triggered_by = [
aws_ecs_service.svc.id
]
}
}precondition and postcondition
You can also use custom condition checks with the lifecycle meta-argument. By adding precondition and postcondition blocks with a lifecycle block, you can specify assumptions and guarantees about how resources and data sources operate.
A precondition is evaluated before Terraform creates or updates the resource. If the condition is false, Terraform aborts the operation with a custom error message.
A postcondition is evaluated after the resource is created or updated. If the condition fails, Terraform will fail the apply and prevent dependent resources from proceeding.
In the example below, a precondition ensures the selected AMI has the correct CPU architecture, and a postcondition asserts that the instance is launched in the expected availability zone:
resource "aws_instance" "example" {
instance_type = "t2.micro"
ami = data.aws_ami.example.id
availability_zone = "eu-central-1a"
lifecycle {
# The AMI ID must refer to an AMI that contains an operating system
# for the `x86_64` architecture.
precondition {
condition = data.aws_ami.example.architecture == "x86_64"
error_message = "The selected AMI must use the x86_64 architecture."
}
# After creation, ensure the instance is really in the desired AZ.
postcondition {
condition = self.availability_zone == "eu-central-1a"
error_message = "Instance was not launched in the expected availability zone."
}
}
}action_trigger
Starting with Terraform 1.14, you can bind provider “actions” to resource lifecycle events using the action_trigger block inside lifecycle. This is useful when you want Terraform to trigger side-effectful automations (for example, invoking a Lambda function or running an external integration) whenever a resource is created or updated, without relying on provisioners or ad-hoc scripts.
The action_trigger block supports three core arguments:
- events – A list of lifecycle events that should trigger the actions. You can use:
- before_create
- after_create
- before_update
- after_update
- actions – An ordered list of actions to invoke when the event fires. Each action is referenced by its full address, for example
action.aws_lambda_invoke.notify_team. - condition (optional) – An expression that must evaluate to true for the actions to run. If the condition is false, Terraform skips the action even if the event occurs.
In the example below, Terraform invokes an AWS Lambda action after an EC2 instance is created or updated, but only if notifications are enabled:
action "aws_lambda_invoke" "notify_team" {
config {
function_name = "notify-team"
payload = jsonencode({
message = "Instance ${aws_instance.example.id} was created or updated."
})
}
}
resource "aws_instance" "example" {
ami = "ami-abc123"
instance_type = "t2.micro"
lifecycle {
action_trigger {
events = [after_create, after_update]
actions = [action.aws_lambda_invoke.notify_team]
condition = var.enable_notifications
}
}
}Actions themselves do not modify Terraform state. Instead, they are designed for day-two operations and other side effects that should run alongside the normal create and update workflow.
Managing Terraform resources with Spacelift
Terraform handles provisioning well, but a secure GitOps approach across teams and stacks needs a platform that orchestrates your Terraform workflows.
Spacelift is the infrastructure orchestration platform built for the AI-accelerated software era. It manages the full lifecycle of both traditional infrastructure as code (IaC) and AI-provisioned infrastructure, giving you a CI/CD workflow for infrastructure, plus features such as:
- Policies (based on Open Policy Agent) – You can control how many approvals you need for runs, what kind of resources you can create, and what kind of parameters these resources can have, and you can also control the behavior when a pull request is open or merged.
- Multi-IaC workflows – Combine Terraform with Kubernetes, Ansible, and other IaC tools such as OpenTofu, Pulumi, and CloudFormation, create dependencies among them, and share outputs.
- Build self-service infrastructure – You can use Templates and Blueprints to build self-service infrastructure; simply complete a form to provision infrastructure based on Terraform and other supported tools.
- AI-powered provisioning and diagnostics – Spacelift Intelligence adds an AI-powered layer for natural language provisioning, diagnostics, and operational insight across your infrastructure workflows.
- Integrations with any third-party tools – You can integrate with your favorite third-party tools and even build policies for them. For example, see how to integrate security tools in your workflows using Custom Inputs.
Spacelift enables you to create private workers inside your infrastructure, which helps you execute Spacelift-related workflows on your end. Read the documentation for more information on configuring private workers.
You can check it for free by creating a trial account or requesting a demo with one of our engineers.
Key takeaways
Understanding the default Terraform resource lifecycle helps you avoid unwanted downtime during apply operations. You can adjust the lifecycle of any resource with the lifecycle meta-argument.
Note: New versions of Terraform are placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.
Orchestrate Terraform deployments with Spacelift
Orchestrate your Terraform workflows and build governed pipelines using policy as code, programmatic configuration, context sharing, drift detection, resource visualization, and many more.
Frequently asked questions
What is the Terraform lifecycle?
The Terraform lifecycle refers to the process Terraform uses to manage infrastructure resources through three core phases: create, update, and destroy. It compares your current configuration to the real infrastructure state, then plans and applies changes to align them. You can control this behavior using the lifecycle block, which supports options like create_before_destroy, prevent_destroy, and ignore_changes to fine-tune how Terraform handles resource updates and deletions.
What are the 5 steps of Terraform?
The five steps of Terraform start with writing infrastructure code using HCL. You then initialize the working directory with terraform init, which sets up required providers. Next, terraform plan lets you preview changes before applying them. With terraform apply, Terraform provisions or updates infrastructure. Finally, terraform destroy removes all managed resources when they’re no longer needed.

