Terraform Actions: Extending Infrastructure Automation Beyond CRUD Operations

Overview
Terraform has become the industry standard for Infrastructure as Code (IaC). It allows teams to define, provision, and manage infrastructure across various cloud providers. At its core, Terraform uses a CRUD (Create, Read, Update, Delete) model. Resources are defined in configuration files, and Terraform manages their lifecycle through state tracking and reconciliation.
However, managing real-world infrastructure often requires tasks that go beyond this traditional CRUD model. For example, operations like invoking serverless functions after deployment, invalidating CDN caches after content updates, smoothly stopping instances, or triggering configuration management tools are imperative actions rather than simple state management. In the past, teams have managed these tasks with external scripts, CI/CD pipelines, or manual console operations, which can fragment workflows and increase maintenance efforts.
Terraform 1.14 introduces actions, a new feature that allows non-CRUD operations directly within Terraform configurations while keeping declarative principles intact. Actions bridge the gap between infrastructure provisioning and operational automation. They allow teams to define both what infrastructure should exist and what operational procedures should be executed.
Understanding Terraform Actions
Actions are operations defined by the provider that trigger side effects without changing Terraform state. Unlike resources, which track persistent infrastructure objects in state files, actions perform temporary operations- they execute once and finish without keeping state.
Core Characteristics
Stateless Execution: Actions do not create, modify, or destroy state-managed resources. They perform their operations and finish, making them ideal for operational triggers, API calls, and integrations.
Provider Integration: Actions are developed by provider teams alongside traditional resources. They offer provider-specific capabilities, such as invoking AWS Lambda, cycling Azure VMs, or dispatching Ansible playbooks.
Dual Invocation Models: Actions support standalone CLI invocation for one-time operations and lifecycle binding for automated execution linked to resource events.
How It Works
Actions introduce a new action block to the Terraform language:
1action "<action_type>" "<symbolic_name>" {2 # meta-arguments: provider, count, for_each34 config {5 # action-specific configuration6 }7}
The action type is provider-prefixed (e.g., aws_lambda_invoke, azure_vm_stop), while the symbolic name allows multiple actions of the same type. The config block contains provider-specific arguments.
Standalone CLI Invocation
Actions can be triggered independently without applying other configuration changes:
1action "aws_lambda_invoke" "notification" {2 config {3 function_name = "send-notification"4 payload = jsonencode({5 message = "Manual deployment notification"6 timestamp = timestamp()7 })8 }9}
Execute with:
1terraform plan -invoke=action.aws_lambda_invoke.notification2terraform apply -invoke=action.aws_lambda_invoke.notification
Only the specified action runs; no resources are modified. Planning operations validate the action configuration before execution. You can only invoke a single action per command.
Lifecycle-Triggered Actions
Bind actions to resource events using the action_trigger block within a resource's lifecycle:
1action "aws_lambda_invoke" "post_deploy" {2 config {3 function_name = "configure-instance"4 payload = jsonencode({5 instance_id = aws_instance.web.id6 environment = var.environment7 })8 }9}1011resource "aws_instance" "web" {12 ami = "ami-091a906f2e1e40076"13 instance_type = "t3.medium"1415 lifecycle {16 action_trigger {17 events = [after_create, after_update]18 actions = [action.aws_lambda_invoke.post_deploy]19 }20 }21}
Supported events include before_create, after_create, before_update, and after_update. Actions trigger automatically during apply operations when events occur.
Conditional Execution
Control action invocation with condition expressions evaluated at plan time:
1variable "enable_notifications" {2 type = bool3 default = false4}56resource "aws_instance" "app" {7 ami = "ami-091a906f2e1e40076"8 instance_type = "t3.small"910 lifecycle {11 action_trigger {12 condition = var.enable_notifications13 events = [after_create]14 actions = [action.aws_lambda_invoke.notifier]15 }16 }17}18
Conditions must use values known at plan time; runtime functions produce unpredictable results.
Practical Use Cases
CloudFront Cache Invalidation
Automatically invalidate CloudFront caches when S3 content updates:
1action "aws_cloudfront_create_invalidation" "cache_clear" {2 config {3 distribution_id = aws_cloudfront_distribution.main.id4 paths = ["/*"]5 }6}78resource "aws_s3_object" "website" {9 bucket = aws_s3_bucket.site.bucket10 key = "index.html"11 content_type = "text/html"12 source = "html/index.html"1314 lifecycle {15 action_trigger {16 events = [after_update]17 actions = [action.aws_cloudfront_create_invalidation.cache_clear]18 }19 }20}21
Terraform waits for invalidation completion before proceeding, ensuring cache consistency.
Graceful Instance Management
Stop EC2 instances gracefully after provisioning:
1action "aws_ec2_stop_instance" "shutdown" {2 config {3 instance_id = aws_instance.batch_processor.id4 }5}67resource "aws_instance" "batch_processor" {8 ami = "ami-091a906f2e1e40076"9 instance_type = "t3.large"10 user_data = file("bootstrap.sh")1112 lifecycle {13 action_trigger {14 events = [after_create]15 actions = [action.aws_ec2_stop_instance.shutdown]16 }17 }18}
This provisions instances with configuration scripts but leaves them stopped until needed, performing graceful shutdowns unlike resource-based state management.
Lambda Function Invocation
Trigger post-deployment configuration via Lambda functions:
1action "aws_lambda_invoke" "setup" {2 config {3 function_name = "infrastructure-setup"4 payload = jsonencode({5 vpc_id = aws_vpc.main.id6 subnets = aws_subnet.private[*].id7 })8 }9}1011resource "aws_vpc" "main" {12 cidr_block = "10.0.0.0/16"1314 lifecycle {15 action_trigger {16 events = [after_create]17 actions = [action.aws_lambda_invoke.setup]18 }19 }20}
Critical Limitations
Actions cannot be used in dependency chains. The following patterns are not supported:
# This will NOT work
1resource "aws_s3_bucket" "data" {2 depends_on = [action.aws_lambda_invoke.setup]3}
Action outcomes are unknown at plan time, preventing Terraform from building dependency graphs. Even depending on resources bound to actions won't wait for action completion—the resource and action execute in parallel.
Actions are designed for side effects that don't produce needed data or modify state. For operations requiring return values or state changes, use ephemeral resources or traditional resource management patterns instead.
Provider Support and Availability
Actions are currently available in:
- AWS Provider: Lambda invocations, EC2 instance operations, CloudFront invalidations, SNS notifications
- Azure Provider: VM power cycling, resource management operations
- Ansible Automation Platform Provider: Event-driven automation triggers



