π Terraform null_resource: Your Secret Weapon for DevOps Automation

Table of contents

When working with Terraform, most people think in terms of resources like EC2 instances, VPCs, security groups, and so on. But what if you need to run a custom script, ping a resource, generate a file, or notify your team β without provisioning anything new?
Thatβs where Terraformβs null_resource
becomes your best friend. π
π What is a null_resource
?
A null_resource
is a special type of resource in Terraform that doesnβt actually create cloud infrastructure. Instead, it allows you to run provisioners (like local shell scripts or commands) as part of your Terraform plan.
πͺ null_resource
: The Do-Something Block
null_resource
is a resource that doesnβt create cloud infra β instead, it executes local or remote provisioners.
resource "null_resource" "notify" {
provisioner "local-exec" {
command = "echo 'Deployment done!'"
}
}
Use cases:
Running scripts after infra is ready
Creating custom files
Sending alerts
Trigger actions based on changes in variables
Perform networking or diagnostic tests (
ping
,dig
, etc.)Generate output files or inventories
π triggers
: Force Re-Execution When Something Changes
triggers
is a map of values that determine when the null_resource
should be recreated.
resource "null_resource" "version_deploy" {
triggers = {
app_version = var.app_version
}
provisioner "local-exec" {
command = "echo 'Deploying app version ${var.app_version}'"
}
}
β
Terraform recreates the null_resource
and re-runs the provisioner if app_version
changes.
You can also use computed values:
triggers = {
timestamp = timestamp()
}
π timestamp()
Function
timestamp()
returns the current UTC time in RFC 3339 format.
Example:
triggers = {
last_updated = timestamp()
}
β
Useful to force recreation of a null_resource
on every apply or log deployment time.
π§ Why Is null_resource
Important?
β
Automation After Deployment
Run custom logic after resources like EC2 instances are ready.
β
Dependency Handling
Use depends_on
and triggers
to control execution order and changes.
β
Bridges the Gaps
Fill the gaps where Terraform doesnβt have a native provider.
β
Useful for CI/CD
Trigger shell scripts, logging tools, or monitoring integrations directly from Terraform.
π§ͺ Real-World Use Cases with Examples
π Send a Slack Message After Deploy
resource "null_resource" "slack_alert" {
provisioner "local-exec" {
command = <<EOT
curl -X POST -H 'Content-type: application/json' \
--data '{"text":"π Deployment complete!"}' \
https://hooks.slack.com/services/YOUR/WEBHOOK/URL
EOT
}
}
πΉ Notify your team automatically β no extra tools needed.
π Ping and Dig EC2 DNS to Test Connectivity
resource "null_resource" "test_connectivity" {
depends_on = [aws_instance.my_ec2]
provisioner "local-exec" {
command = <<EOT
echo "Pinging ${aws_instance.my_ec2.public_dns}..."
ping -c 3 ${aws_instance.my_ec2.public_dns}
echo "DNS Lookup:"
dig +short ${aws_instance.my_ec2.public_dns}
EOT
}
}
πΉ Helpful for quickly verifying network reachability and DNS resolution from your automation.
π Save EC2 IPs to a File (Inventory or Config)
resource "null_resource" "write_ip_to_file" {
depends_on = [aws_instance.my_ec2]
provisioner "local-exec" {
command = <<EOT
echo "${aws_instance.my_ec2.public_ip}" >> ec2_ips.txt
EOT
}
}
πΉ Useful when you need a simple, up-to-date file of all deployed instance IPs β for use in Ansible, SSH scripts, or monitoring tools.
π Re-Run Task When a Variable Changes (e.g., App Version)
resource "null_resource" "version_task" {
triggers = {
version = var.app_version
}
provisioner "local-exec" {
command = "echo 'Deploying version ${var.app_version}'"
}
}
πΉ Great for re-triggering builds or deployments only when the app version changes.
π― What Are Provisioners in Terraform?
Provisioners allow you to execute scripts or commands after a resource is created (or before it's destroyed). Theyβre useful when:
You want to configure a server after deployment
Trigger external scripts
Generate or log files
β οΈ Use provisioners sparingly. They're not idempotent and can break automation if not handled carefully.
π§ 1. local-exec
: Run a Local Command
Runs a command on the machine where Terraform is executed (your laptop, CI runner, etc.).
data "aws_vpc" "main" {
default = true
}
output "vpc_id" {
value = data.aws_vpc.main.id
}
output "vpc_cidr" {
value = data.aws_vpc.main.cidr_block
}
resource "null_resource" "example" {
triggers = {
#Use any one of below but trigger is must
id = timestamp()
timestamp = timestamp()
last_updated = data.aws_vpc.main.id
}
provisioner "local-exec" {
command = "echo ${data.aws_vpc.main.id} && echo ${data.aws_vpc.main.cidr_block} >> vpc_info.txt"
}
}
β Great for local logging, notifying CI/CD tools, or triggering CLI tools.
π 2. file
: Copy Files to Remote Machines
The file
provisioner lets you upload a file or directory to a remote machine over SSH or WinRM.
resource "aws_instance" "web" {
ami = "ami-123456"
instance_type = "t2.micro"
}
resource "null_resource" "upload_config" {
provisioner "file" {
source = "config.yaml"
destination = "/tmp/config.yaml"
}
connection {
type = "ssh"
host = aws_instance.web.public_ip
user = "ec2-user"
private_key = file("~/.ssh/id_rsa")
}
}
β Useful for uploading config files, SSL certs, or bootstrap scripts.
π 3. remote-exec
: Run Commands on Remote Machines
Runs inline commands on the target machine via SSH or WinRM.
provisioner "remote-exec" {
inline = [
"sudo apt update",
"sudo systemctl start nginx"
]
}
β Perfect for bootstrapping servers β though consider tools like Ansible for complex tasks.
π§ Best Practices
β
Prefer declarative resources over provisioners when possible
β
Use null_resource
for quick glue logic or diagnostics
β
Keep provisioners short and idempotent
β
Use triggers
wisely to avoid unnecessary re-runs
β Donβt rely on provisioners for complex provisioning β use tools like Ansible, Packer, etc.
β Summary
Feature | Purpose |
local-exec | Run local scripts/commands |
file | Upload files to remote instances |
remote-exec | Run commands remotely over SSH |
null_resource | Logical block to trigger provisioners |
triggers | Force re-run when input changes |
timestamp() | Dynamic value to always trigger new apply |
β Final Thoughts
Terraforms null_resource
is like duct tape β not always pretty, but super handy when nothing else works.
If youβve ever needed to run a command or script after Terraform does its job, this is your go-to tool.
Subscribe to my newsletter
Read articles from Venkata Pavan Vishnu Rachapudi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Venkata Pavan Vishnu Rachapudi
Venkata Pavan Vishnu Rachapudi
I'm Venkata Pavan Vishnu, a cloud enthusiast with a strong passion for sharing knowledge and exploring the latest in cloud technology. With 3 years of hands-on experience in AWS Cloud, I specialize in leveraging cloud services to deliver practical solutions and insights for real-world scenarios. Whether it's through engaging content, cloud security best practices, or deep dives into storage solutions, I'm dedicated to helping others succeed in the ever-evolving world of cloud computing. Let's connect and explore the cloud together!