How to use Terraform Null Resource – Examples!
If you’ve worked with Terraform for a while, you’ve probably hit situations where you need to run something that doesn’t fit neatly into a cloud resource. Maybe you need to execute a local script after provisioning, or generate a config file based on variables. That’s where the null_resource comes in.
In this post, I’ll walk through several practical examples of how to use null_resource in real Terraform projects.
What is Terraform Null Resource?
The name sounds weird, but it’s straightforward: a null_resource doesn’t create any actual infrastructure. It’s just a container that lets you run provisioners when you need them.
The null_resource solves a real problem. Sometimes you need to run commands or scripts that interact with resources you’ve already created. Without it, you’d have no way to tell Terraform about these dependencies. With it, you can hook into Terraform’s lifecycle and execute arbitrary commands.
How Terraform Null Resource Works
To use null_resource effectively, you need to understand two concepts: provisioners and triggers.
Provisioners define what to execute. Triggers define when to execute it.
You can use null_resource in several contexts:
2- Combined with Terraform Count
3- With Terraform Data
4- With Terraform Local Variables
5- In Output blocks
6- With Conditional and Dynamic Expression blocks
Here’s a basic example:
resource "null_resource" "configmap" {
triggers = {
value = var.some_id
}
provisioner "local-exec" {
command = <<EOT
kubectl apply -f ${local.configmap_auth_file} --kubeconfig ${var.kubeconfig_path}
EOT
}
}
What do triggers do?
Inside the triggers block, you specify key-value pairs. Terraform doesn’t care about the names you choose. What matters is when those values change: Terraform will re-execute the null_resource block.
The null_resource behaves like a regular resource in your Terraform state, but it doesn’t deploy anything to the cloud. It just follows the standard Terraform lifecycle.
Terraform Provisioner
Inside the null_resource block, you specify a provisioner to define what to execute and where. In the example above, the “local-exec” provisioner runs the command on your local machine.
Examples
Let’s look at some practical scenarios.
Waiting for EKS cluster creation
A common problem: you need to run a command only after an EKS cluster exists. If you try to run it before the cluster is ready, things fail.
You can use depends_on to force Terraform to wait:
resource "null_resource" "configmap" {
triggers = {
value = var.some_id
}
depends_on = [aws_eks_cluster.my_cluster]
provisioner "local-exec" {
command = <<EOT
kubectl apply -f ${local.configmap_auth_file} --kubeconfig ${var.kubeconfig_path}
EOT
}
}
This tells Terraform to create the EKS cluster first, then run the null_resource.
Triggering on file changes
Want to re-run a command when a file changes? Use the md5 function to detect modifications:
resource "local_file" "backup_file" {
content = templatefile("${path.module}/config.tpl", {
host = var.host
})
filename = "${path.module}/config.yml"
}
resource "null_resource" "backup_file" {
triggers = {
file_changed = md5(local_file.backup_file.content)
}
provisioner "local-exec" {
command = "kubectl apply -f ${path.module}/config.yml"
}
depends_on = [local_file.backup_file]
}
When the file content changes, md5 returns a different value, which triggers Terraform to run the provisioner again.
Always running the provisioner
Sometimes you want the provisioner to run every time, not just when something changes. Use timestamp() in the trigger:
resource "null_resource" "configmap" {
triggers = {
always_run = "${timestamp()}"
}
provisioner "local-exec" {
command = <<EOT
cat << EOF > /tmp/config
REDIS_URL: "${data.remote_state.get.outputs.redis_primary_endpoint}:6379"
KAFKA_CONSUMERS_BOOTSTRAP_SERVERS: "${data.remote_state.get.outputs.msk_bootstrap_brokers}"
EOF
EOT
}
}
The timestamp() function returns a new value on each run, so Terraform always sees a change and re-executes the provisioner.
Running without triggers
You can omit triggers entirely. Terraform will create an entry in the state and run the provisioner once, on the first apply. Subsequent applies won’t trigger it again:
resource "null_resource" "config" {
provisioner "local-exec" {
command = "kubectl apply -f ${path.module}/config.yaml"
}
depends_on = [kubernetes_namespace.app, local_file.app]
}
This pattern works well for one-time tasks like deploying configuration files that don’t need duplication.
A note on modern Terraform
If you’re modernizing Terraform code, also check for_each vs count and Terraform testing in 2026.
Since Terraform 1.7, the null_resource is deprecated. For new projects, use the terraform_data resource instead, which provides the same functionality. If you’re on an older version, null_resource still works but plan to migrate eventually. The newer terraform_data vs null_resource guide walks through where the modern boundary sits and when keeping null_resource is still pragmatic.
These examples should give you a solid foundation for using null_resource in your own infrastructure. If you’ve found other useful patterns, I’d love to hear about them.
Comments