At HashiConf this week, HashiCorp rolled out something many of us DevOps folks have been wishfully dreaming about: Terraform Actions.
If you've worked with Terraform for longer than five minutes, you know the philosophy: immutable infra only! Apply, destroy, and between those two, you're not really supposed to do anything.
But let's admit it, we've all done stuff. Executed a script on a VM, initiated a migration, or invoked something without modifying the .tf
file. Most often, the "solution" was provisioners or some other workarounds, which, if we're being honest, always seemed like duct-taping your car bumper. HashiCorp docs even do not recommend them saying: "try not to use these."
So what's new?
Terraform Actions is now a first-class block within HCL that allows you to define what should occur between the creation and teardown of infrastructure.
It's as if HashiCorp is declaring:
"Fine, we know you're going to do this anyway. Here's a tidy, supported means of doing it."
Some actual applications:
- Run DB migrations post-deployment of a new instance.
- Rotate secrets without taking everything down.
- Make config tweaks safely without violating immutability guarantees.
- Invoke a Lambda, stop an AWS EC2 instance, invalidate an AWS CloudFront cache, etc.
Terraform v1.14.0-beta2 (pre-release) just dropped yesterday (Sept 25, 2025). As of today (the very next day), in the AWS provider I only see three Actions available so far
aws_lambda_invoke
,aws_cloudfront_create_invalidation
, andaws_ec2_stop_instance
.
To really see the difference between the old way and the new way, let’s take a simple example: say we need to invoke a Lambda function (yep, in AWS).
Before (old school way!):
If any existing Lambda is to be invoked, we could use a data block:
data "aws_lambda_invocation" "call_hellolambda" {
function_name = "hellolambda"
input = jsonencode({ })
}
output "lambda_response" {
value = data.aws_lambda_invocation.call_hellolambda.result
}
Sure, this works — but it runs the Lambda every single time you do terraform apply
. Not exactly flexible.
OR
If we wish to invoke the lambda just after creation, we can use a resource block:
resource "aws_lambda_function" "example" {
function_name = "hellolambda"
}
resource "aws_lambda_invocation" "invoke_after_creation" {
function_name = aws_lambda_function.example.function_name
depends_on = [aws_lambda_function.example]
input = jsonencode({
})
}
Here the Lambda gets invoked right after creation. Sounds good… until you realize it’s now stuck to the lifecycle. If nothing changes in the resource config, Terraform won’t re-invoke it.
OR
We were using clunky, duct-taping provisioners:
provisioner "remote-exec" {
inline = ["aws lambda invoke --function-name hellolambda /tmp/lambda_output.txt"]
connection { ... }
}
This is the duct-tape-and-prayers method. It’ll run… until the day it doesn’t, and then you’re the lucky one ssh’ing into a box at 2 AM.
After Terraform Actions (the new way of On-Demand Invocations!!)
You can configure (the new) action blocks in your Terraform configuration that are not referenced anywhere else in your code like below:
action "aws_lambda_invoke" "test" {
config {
function_name = "hellolambda"
payload = jsonencode({})
}
}
This standalone action can be triggered using the Terraform CLI. To invoke them, you use the -invoke=<action address>
flag with the terraform plan
or terraform apply
commands:
terraform apply -auto-approve -invoke=action.aws_lambda_invoke.test
You would see output like this:
The above example is just for ad-hoc action which you can trigger using the CLI. You can also bind actions to the lifecycle of a resource.
Why Terraform Actions Feel Like an Upgrade
The good thing about Terraform Actions is that they finally give us a clean, native way to run operations in Terraform without resorting to hacks. A few reasons this matters:
- You can perform on-demand tasks like invoking a Lambda function, rotating a secret, stopping/starting an EC2, without tying it to the whole “create/destroy” lifecycle.
- No more duct-taping SSH commands or local-exec scripts just to run a one-off task.
- Actions are visible and auditable in your config, instead of tasks/operations hiding in random shell scripts.
- They fit naturally into CI/CD pipelines hence safer, easier, and a lot less fragile than the workarounds most of us have been using.
From the release notes:
Actions are provider-defined and meant to codify use cases outside the normal CRUD model in your Terraform configuration. Providers can define Actions likeaws_lambda_invoke
oraws_cloudfront_create_invalidation
that do something imperative outside of Terraform’s normal CRUD model. You can configure such a side-effect with an action block and have actions triggered through the lifecycle of a resource or through passing the-invoke
CLI flag.
Conclusion
Terraform Actions are still in beta and starting with just a handful of examples, but honestly, it already feels like a fresh breeze after years of duct-taping solutions.
It surely will ease our pain of playing with various workarounds. This was my initial reaction article on this wonderful addition by HashiCorp.
Excited to explore other new features and enhancements in this latest version 1.14 and cannot wait to drop more such content in "The Devops Drop".
Subscribe to The Devops Drop on LinkedIn : https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7375749694414782466
Top comments (0)