Leveraging null_resource in Terraform for Complex Operations

Let’s not kid ourselves — null_resource
is the duct tape of Terraform. It doesn’t provision a VM, it doesn’t configure a VPC, and it sure as hell doesn’t play nice with cloud-native best practices. But used wisely, it’s the unsung hero of CI/CD glue code and one-off automation.
In this post, we’ll get tactical with null_resource
, walk through real-world use cases, and contrast it with the newer (and cleaner) terraform_data
resource — so you know when to reach for which tool without feeling dirty.
Terraform Resources 101 (Quick Recap)
Terraform’s core mechanic is the resource
block — which tells the provider, “Hey, make this thing exist.”
resource "azurerm_windows_function_app" "app" { name = "example-function-app" location = "East US"}
That’s your bread-and-butter declaration: define the desired state, let Terraform do the heavy lifting.
But sometimes, you don’t want to create anything in the cloud. You just want Terraform to do something — run a script, call a webhook, poke Jenkins with a stick. That’s where null_resource
comes in.
What the Hell is null_resource
?
null_resource
is exactly what it sounds like: a Terraform resource that manages nothing. No infrastructure, no API objects — just logic.
But here’s the trick: it still behaves like a real resource. It supports lifecycle actions (create
, destroy
, etc.), can depend on other resources, and — most importantly — supports provisioners and triggers.
resource "null_resource" "example" { triggers = { always_run = timestamp() }
provisioner "local-exec" { command = "echo 'Triggering follow-up actions'" }}
This block runs every time because the timestamp()
changes on every plan. Perfect for when you need to kick off external processes after infra changes.
Triggers: The Secret Sauce
Triggers are the magic behind null_resource
. They control when it gets re-executed — not based on resource state, but on changes to arbitrary data.
Example:
triggers = { hash = filemd5("config.json")}
Now your null_resource
only re-runs when config.json
changes. This is gold in CI/CD setups where you’re tracking file changes, API responses, or even environment variables.
Real-World Scenarios (Where null_resource
Actually Earns Its Keep)
1. Post-Provision Webhook Pings
Say you’ve just spun up infrastructure and need to notify an external system — like triggering a GitHub Actions workflow or pinging a Slack webhook.
resource "null_resource" "notify" { triggers = { infra_version = var.release_tag }
provisioner "local-exec" { command = "curl -X POST https://hooks.slack.com/services/XXX -d 'Terraform apply complete: ${var.release_tag}'" }}
Why not just use curl
in your pipeline? Because this runs inside Terraform’s dependency graph — meaning it won’t fire unless upstream infra actually changed.
2. Conditional Execution Based on Dynamic Data
Let’s say you’re pulling in an Azure storage account and want to trigger an action when its key changes.
data "azurerm_storage_account" "example" { name = "examplestorageaccount" resource_group_name = "my-rg"}
resource "null_resource" "trigger_on_key_change" { triggers = { key = data.azurerm_storage_account.example.primary_access_key }
provisioner "local-exec" { command = "echo 'Access key has changed, executing...'" }}
This is ideal for invalidating cache, refreshing secrets, or kicking off a rotation script. And yes, it’s hacky — but it works.
3. Smoke Testing After Apply
Spin up resources, run a curl test to confirm the service responds, fail fast if it doesn’t.
resource "null_resource" "smoke_test" { depends_on = [azurerm_function_app.example]
provisioner "local-exec" { command = "curl -sf http://example-app.azurewebsites.net/health || exit 1" }}
You’d be surprised how often infra “successfully applies” but the app is dead. This catches those cases before CI marks the job green.
The Cleaner Alternative: terraform_data
(1.4+)
Starting in Terraform 1.4, HashiCorp added a new built-in: terraform_data. It does what null_resource
does — just with less baggage and without relying on a provider plugin.
resource "terraform_data" "run_command" { provisioner "local-exec" { command = "echo 'Still works!'" }}
You lose triggers (for now), but it’s first-party and cleaner for one-off tasks. Ideal for scripting or injecting data during apply without pretending to be a cloud resource.
null_resource vs terraform_data: When to Use What
Use Case | Use null_resource | Use terraform_data |
---|---|---|
Triggering on external data changes | ✅ | ❌ |
One-time scripting | ✅ | ✅ |
CI/CD glue between real resources | ✅ | ✅ |
Need clean, future-proof setup | ❌ (plugin) | ✅ (built-in) |
Want something to break later | ✅ | ❌ |
TL;DR: If you need triggers, stick with null_resource
. Otherwise, migrate to terraform_data
as the cleaner, future-proof alternative.
Final Thoughts
If Terraform were a programming language, null_resource
would be its eval()
— powerful, dangerous, and misused 99% of the time.
But in the hands of someone who knows what they’re doing? It bridges the gap between infrastructure and orchestration — especially in CI/CD pipelines, edge cases, or situations where Terraform alone can’t model reality.
Just don’t go overboard. If you find yourself writing a bash script inside a null_resource
that runs a Python script that generates more HCL… maybe reconsider your life choices.
Pro Tip: Pair null_resource
with depends_on
and triggers
for controlled chaos. Or use terraform_data
if you want to sleep at night.
Social Channels
- 🎬 YouTube
- 🐦 X (Twitter)
- 🐘 Mastodon
- 🧵 Threads
- 🧊 Bluesky
- 🎥 TikTok
- 📣 daily.dev Squad
- ✈️ Telegram
- 🐈 GitHub
Community of IT Experts
- 👾 Discord
Is this content AI-generated?
No. Every article on this blog is written by me personally, drawing on decades of hands-on IT experience and a genuine passion for technology.
I use AI tools exclusively to help polish grammar and ensure my technical guidance is as clear as possible. However, the core ideas, strategic insights, and step-by-step solutions are entirely my own, born from real-world work.
Because of this human-and-AI partnership, some detection tools might flag this content. You can be confident, though, that the expertise is authentic. My goal is to share road-tested knowledge you can trust.