Terraform actions
An action is an imperative, one-shot operation defined by a provider — invoke a Lambda function, run an Ansible playbook, restart a service. Unlike resources, actions do not create infrastructure and are not tracked in state: they simply run. Actions were introduced in Terraform 1.14 and replace common workarounds such as null_resource with local-exec provisioners.
flowchart LR
resource["resource block"] -- "declares" --> infra["Infrastructure<br/>(tracked in state)"]
action["action block"] -- "performs" --> op["One-shot operation<br/>(not tracked in state)"]
Declare an action with an action block. Like a resource, it has a type and a local name, and provider-specific arguments go inside a nested config block:
| |
There are two ways to run an action.
Triggered by resource lifecycle events — add an action_trigger block inside a resource’s lifecycle block. The events argument accepts before_create, after_create, before_update, and after_update:
| |
Invoked ad hoc from the CLI — run only the action, skipping everything else in the configuration:
terraform apply -invoke=action.aws_lambda_invoke.db_migrationThis makes actions a natural fit for day-2 operations: recurring operational tasks like triggering a database migration or rotating a credential, done with the same tooling and configuration as your infrastructure.
Best practices
- Use actions instead of
null_resourceorlocal-execprovisioners for imperative operations — they are first-class, planable, and provider-supported. - Keep actions idempotent where possible; a trigger like
after_updatemay fire more often than you expect (for example when Terraform detects drift). - Prefer lifecycle triggers for operations tied to a resource’s life, and
-invokefor operational tasks you run on demand. - Remember actions are not tracked in state — Terraform will not re-run a failed action automatically on the next apply unless it is triggered again.