Devops

Terraform Null Resource: When and How to Use It Effectively

April 7, 2026
Published
#Automation#DevOps#Infrastructure as Code#Provisioners#Terraform

At some point while working with Terraform, you’ll run into a situation where you need to do something that isn’t tied to a specific resource—run a script, call an API, or glue together steps that Terraform doesn’t natively support. That’s where the Terraform null resource quietly becomes one of the most useful (and misunderstood) tools in your toolkit.

What is a Terraform Null Resource?

A null resource is exactly what it sounds like: a resource that doesn’t manage infrastructure. Instead, it acts as a placeholder that allows you to attach provisioners or define execution logic within your Terraform workflow.

Think of it as a bridge between Terraform’s declarative model and imperative actions.

A minimal example

TEXT
1resource "null_resource" "example" {
2  provisioner "local-exec" {
3    command = "echo Hello, Terraform"
4  }
5}

This doesn’t create any infrastructure, but Terraform will execute the command during the apply phase.

Why Use Null Resources?

Terraform is designed to describe infrastructure, not run scripts. Still, real-world workflows often require:

  • Running initialization scripts
  • Triggering CI/CD steps
  • Calling external services
  • Performing one-off configuration tasks

A Terraform null resource lets you integrate these steps without leaving your Terraform codebase.

Triggers: The Real Power Behind Null Resources

By default, a null resource runs only once. That’s rarely useful. The magic comes from triggers, which tell Terraform when to re-run the resource.

Example with triggers

TEXT
1resource "null_resource" "build" {
2  triggers = {
3    version = "${var.app_version}"
4  }
5
6  provisioner "local-exec" {
7    command = "./build.sh ${var.app_version}"
8  }
9}

Whenever app_version changes, Terraform will destroy and recreate this null resource—rerunning the script.

In practice, triggers turn a null resource into a controlled execution unit inside Terraform.

Common Use Cases

1. Running Local Scripts

Need to compile assets, generate configs, or run a custom CLI?

TEXT
1resource "null_resource" "compile_assets" {
2  triggers = {
3    timestamp = timestamp()
4  }
5
6  provisioner "local-exec" {
7    command = "npm run build"
8  }
9}

Using timestamp() forces execution on every apply. Useful, but easy to abuse.

2. Remote Commands via SSH

You can execute commands on a remote machine using remote-exec:

TEXT
1resource "null_resource" "configure_server" {
2  provisioner "remote-exec" {
3    inline = [
4      "sudo apt update",
5      "sudo apt install -y nginx"
6    ]
7
8    connection {
9      type     = "ssh"
10      user     = "ubuntu"
11      private_key = file("~/.ssh/id_rsa")
12      host     = aws_instance.web.public_ip
13    }
14  }
15}

This is often used for bootstrapping when cloud-init or user_data isn't enough.

3. Dependency Orchestration

Sometimes you just need to enforce ordering:

TEXT
1resource "null_resource" "wait_for_db" {
2  depends_on = [aws_db_instance.main]
3
4  provisioner "local-exec" {
5    command = "echo Database is ready"
6  }
7}

This can help coordinate steps across unrelated resources.

4. API Calls or Webhooks

Trigger external systems:

JSON
1resource "null_resource" "notify" {
2  provisioner "local-exec" {
3    command = "curl -X POST https://example.com/deploy"
4  }
5}

Where Things Get Tricky

Null resources are powerful, but they come with trade-offs.

They break Terraform’s declarative model

Terraform is built around state and desired outcomes. Null resources introduce imperative steps that Terraform can’t fully track or validate.

Provisioners are a last resort

Even HashiCorp recommends using provisioners sparingly. If there’s a native Terraform resource or cloud-native solution, prefer that.

Idempotency becomes your responsibility

Terraform won’t ensure your scripts are safe to run multiple times. You must handle:

  • Duplicate executions
  • Partial failures
  • Side effects

A Better Pattern: Use Data + Resources First

Before reaching for a null resource, ask:

  • Can this be handled by an existing provider?
  • Can I use user_data or cloud-init?
  • Can a CI/CD pipeline handle this instead?

If the answer is yes, skip null resources.

Combining Null Resource with Files

A practical pattern is using file hashes as triggers.

TEXT
1resource "null_resource" "deploy_config" {
2  triggers = {
3    config_hash = filemd5("config.yaml")
4  }
5
6  provisioner "local-exec" {
7    command = "./deploy-config.sh"
8  }
9}

Now the script only runs when the file changes.

When You Should Use Terraform Null Resource

  • You need lightweight orchestration inside Terraform
  • No provider exists for your task
  • You need controlled script execution tied to state

When You Should Avoid It

  • For complex workflows better handled by CI/CD
  • When idempotency is critical and hard to guarantee
  • If a native Terraform resource exists

Final Thought

The Terraform null resource isn’t flashy, but it’s incredibly useful when used with restraint. Treat it as a glue layer—not a foundation. If you lean on it too heavily, your infrastructure code starts to behave less like Terraform and more like a shell script with state.

Used carefully, though, it fills the exact gaps Terraform intentionally leaves open.

Comments

Leave a comment on this article with your name, email, and message.

Loading comments...

Similar Articles

More posts from the same category you may want to read next.

Share: