


























Terraform’s declarative model doesn’t fit every situation. If you want to restart a VM after pushing a new configuration or to invoke a Lambda once your stack is up, Terraform’s support for this wasn’t first-class. Until recently, this was handled with null_resource, local-exec provisioners, external CI scripts, or Bash wrappers.
Once Terraform 1.14 was released, HashiCorp introduced a new block called action that brings imperative, provider-defined operations into the Terraform language. This new feature is called actions, and it executes around your resources without becoming part of the state, which means you can finally express day-two operations declaratively.
In this article, we will walk through what the Terraform action block is, how it differs from resources and data sources, which providers currently support Terraform actions, how to invoke a Terraform action, and some practical use cases.
What we’ll cover:
null_resource, local-exec, and bash workarounds.terraform apply -invoke, or by binding it to a resource lifecycle with an action_trigger block (create and update events only, no destroy yet).The action block was introduced in Terraform 1.14 and allows you to declare a provider-defined operation that runs imperatively when invoked. There are many operations, such as calling a Lambda function, restarting an EC2 instance, or sending a Slack message.
These are not CRUD operations against managed resources, they are actually one-shot tasks that must be performed as part of your infrastructure workflow.
The action block replaces workarounds like null_resource with local-exec, terraform_data triggers, or pre/post-apply bash scripts in your CI pipeline. So, actions are implemented as part of the provider binary, just like resources, data sources, and therefore they benefit from typed inputs and validation.
The basic syntax of an action block looks similar to this:
action "<TYPE>" "<LABEL>" {
config {
<provider-specific arguments>
}
}The action TYPE is defined by the provider, for example, aws_lambda_invoke, and the LABEL can be named as you wish, as you do for your resources. You can reference the action wherever you want in your configuration using the action.<TYPE>.<LABEL> syntax.
Using the AWS provider, let’s see an example of how to invoke a Lambda function:
action "aws_lambda_invoke" "notify_team" {
config {
function_name = "deployment-notifier"
payload = jsonencode({
environment = "production"
event = "deployment_complete"
})
}
}In the config block, you can find the provider-specific arguments. What you can add inside it depends entirely on the action type.
You can check your provider’s documentation for the available action types and their config arguments. These are available under each category of resources that support them in the same way as resources and data sources are.

Action blocks support a subset of meta-arguments you already know from resources. You can use:
count: so you can run the same action multiple times with the same configurationfor_each: used for running the action once per element of a map or set of stringsprovider: with which you can pin the action to a specific provider configuration (if you are using multiple providers in your configuration)Let’s see an example using count and provider:
action "aws_lambda_invoke" "example" {
count = 3
config {
function_name = "my_function"
payload = jsonencode({
instance = count.index
})
}
}In this example, as count was used, you won’t be able to leverage for_each. As with resource blocks, they cannot be used simultaneously.
As you already know, resources are stateful. Terraform creates resources, tracks them in state, reconciles any drift, and destroys them when you remove them from configuration if you run terraform apply again. Data sources are read-only and fetch information about existing infrastructure to use in your plan and apply.
Actions are different. Unlike stateful resources and read-only data sources, action blocks are stateless, lifecycle-free operations you explicitly invoke to execute, with results that aren’t tracked in state or available at plan time.
Here is a list of characteristics that highlight their uniqueness:
action block explicitly, either via CLI or through an action_trigger, in order to execute it.depends_on for another resource; there are no result-based dependencies.The catalog of provider support for action is small but still growing. This is understandable as it is in its early days. You can find below the current list of providers that support Terraform actions:
aws_lambda_invoke, aws_ec2_stop_instance, and aws_cloudfront_create_invalidationazurerm_virtual_machine, which helps you start, stop, or restart VMslocal_command for running local commands, useful for testing and simple workflowsYou can check the Terraform Registry to see whether your provider has shipped action types. Other major providers, such as GCP and Kubernetes, are still catching up.
OpenTofu, for example, does not yet support action blocks, but has an open issue (#3309) for it.
Before deciding to refactor your codebase and to use actions, be aware that, while actions solve real problems, they also come with constraints. Here are the main limitations as of Terraform 1.14:
before_create, after_create, before_update, and after_update. You cannot run an action as part of the destroy phasedepends_on, as you cannot use it in another resource’s configuration.There are two ways to invoke an action: by CLI or by action_trigger. Let’s take them one by one.
So, the simplest way to run an action is from the command line. You can use the -invoke flag with the terraform plan or terraform apply commands, passing the full name of the action:
terraform apply -invoke=action.aws_lambda_invoke.exampleWhen you invoke an action via CLI, Terraform runs only the action and excludes all other configuration changes. This helps a lot for day-two operations, such as stopping dev VMs to save costs overnight or triggering a deployment notification on demand.
The second way to invoke an action is by binding it to a resource’s lifecycle events using the action_trigger block:
resource "aws_lambda_function" "api_handler" {
function_name = "my-api-handler"
runtime = "python3.14"
handler = "index.handler"
role = aws_iam_role.lambda_role.arn
filename = "function.zip"
lifecycle {
action_trigger {
events = [after_create, after_update]
actions = [action.aws_lambda_invoke.notify_team]
}
}
}
action "aws_lambda_invoke" "notify_team" {
config {
function_name = "deployment-notifier"
payload = jsonencode({
message = "Lambda function deployed"
})
}
}The action_trigger block supports three arguments:
before_create, after_create, before_update, and after_updateYou can add multiple action_trigger blocks in the same lifecycle block. Once you run the terraform apply command, Terraform evaluates all triggers and invokes the actions whose conditions are satisfied. If an action fails, Terraform will stop further executions.
Here are the use cases where you will reach for an action block most often:
aws_cloudfront_create_invalidation to automatically invalidate your CloudFront distributionazurem_virtual_machine_powerActions are a great choice for day-two operations that sit between or around your CRUD resources. They can be highly useful when you want side effects to be visible in your Terraform plan output instead of forgotten in a Makefile or CI script.
Action blocks are powerful, but on their own, they won’t solve the problems you have with running Terraform safely at scale. In most cases, you need policy enforcement, drift detection, run visibility, and a way to manage multiple stacks across cloud providers.
Spacelift is the infrastructure orchestration platform built for the AI-accelerated software era, managing the full lifecycle of both traditional IaC and AI-provisioned infrastructure.
It helps you manage all your IaC, Ansible, and Kubernetes from a single control plane, making it easy to implement a GitOps workflow that handles all your governance, including built-in policy as code, drift detection and remediation, dependency management across your stacks, self-service infrastructure with Templates, and more.
Spacelift Intelligence adds an AI-powered layer for natural language provisioning, diagnostics, and operational insight across both your traditional and AI-driven workflows.
Learn more about what you can do with Spacelift here.
You can take advantage of running Terraform actions directly through binding them to a resource’s lifecycle events, or, if you’d like to replicate the CLI approach, you can take advantage of the built-in Spacelift Tasks for your Stacks.
You no longer have to lean on null_resources, local-exec provisioners, or external bash wrappers to invoke Lambda, restart a VM, or hit a webhook. The action block feature shipped by Terraform 1.14 will help you with these tasks natively.
The action block feature is still in its early days, so it has plenty of room for improvement. As mentioned, provider coverage is limited, destroy-time events are not yet supported, and you cannot use action results as dependencies for other resources.
Spacelift can help you to take your Terraform workflows further, including configurations that use action blocks. It provides an orchestration layer to manage everything in one place, with policies, drift detection, and run visibility.
If you want to learn more about Spacelift, book a demo with one of our engineers.
Actions were announced at HashiConf 2025 and shipped in Terraform 1.14.0, released GA on November 19, 2025.
At launch, actions were available in AWS, Microsoft Azure, and the Red Hat Ansible Automation Platform (AAP) providers (plus the Local provider), but since actions are provider-defined, support varies by provider. Check each provider’s Registry docs.
A resource is managed through the full create/read/update/delete cycle and tracked in state, whereas actions are preset provider operations that trigger automations outside Terraform and do not affect resource state.
With a provisioner, you write your own script (coupling infrastructure to external CLIs), while an action is defined by the provider and runs using the provider’s existing authentication and context, eliminating the need for external CLIs, making actions the modern replacement for most provisioner and null_resource workarounds.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。