Monthly musings with Marcin Wyszynski, technical co-founder of Spacelift

📨 Join the Newsletter

Terraform

Terraform Resource Lifecycle Meta-Argument [Examples]

The Lifecycle of a Terraform Resource

In this article, we will take a look at the various stages a Terraform resource goes through during its lifetime. We will look at the default resource behavior before looking at the lifecycle meta-argument, which can allow you to customize that behavior.

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 make the real infrastructure match 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.

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 before re-creating it, if certain setting changes within the resource configuration block means, this must happen on the given platform. For example, changing the name of a VM in Azure is not possible without first destroying the VM. It is destroyed and then recreated with the new VM name specified in the settings of 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:

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 is possible using the lifecycle meta-argument. This is useful in scenarios when you need to protect items from getting changed or destroyed.

create_before_destroy

lifecycle {
  create_before_destroy = true
}

prevent_destroy

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.

ignore_changes

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.

Key Points

Understanding the default behavior of the Terraform resource lifecycle can help avoid unwanted downtime when Terraform executes operations. The lifecycle of every resource can be manipulated as needed using 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.

Manage Terraform Better and Faster

If you are struggling with Terraform automation and management, check out Spacelift. It helps you manage Terraform state, build more complex workflows, and adds several must-have capabilities for end-to-end infrastructure management.

Start free trial

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.

The Practitioner’s Guide to Scaling Infrastructure as Code

Transform your IaC management to scale

securely, efficiently, and productively

into the future.

ebook global banner
Share your data and download the guide