πŸš€ Terraform null_resource: Your Secret Weapon for DevOps Automation

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.).

Terraform null_resource Execution Flow

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

FeaturePurpose
local-execRun local scripts/commands
fileUpload files to remote instances
remote-execRun commands remotely over SSH
null_resourceLogical block to trigger provisioners
triggersForce 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.

0
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!