Terraform actions

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:

main.tf
1
2
3
4
5
6
7
8
action "aws_lambda_invoke" "db_migration" {
  config {
    function_name = "db-migration"
    payload = jsonencode({
      environment = "production"
    })
  }
}

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:

main.tf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
resource "aws_instance" "app" {
  ami           = "ami-0123456789abcdef0"
  instance_type = "t3.micro"

  lifecycle {
    action_trigger {
      events  = [after_create, after_update]
      actions = [action.aws_lambda_invoke.db_migration]
    }
  }
}

Invoked ad hoc from the CLI — run only the action, skipping everything else in the configuration:

terraform apply -invoke=action.aws_lambda_invoke.db_migration

This 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.

Providers must explicitly implement actions. Check your provider’s documentation to see which actions are available.

Best practices

  • Use actions instead of null_resource or local-exec provisioners for imperative operations — they are first-class, planable, and provider-supported.
  • Keep actions idempotent where possible; a trigger like after_update may fire more often than you expect (for example when Terraform detects drift).
  • Prefer lifecycle triggers for operations tied to a resource’s life, and -invoke for 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.