TerraformPilot

Terraform

Terraform Null Provider and Null Resources

Use Terraform null_resource for custom provisioners, triggers, and workarounds. Local-exec, remote-exec, dependency management, and when to use...

LLuca Berton1 min read

Quick Answer

#
resource "null_resource" "deploy" {
  triggers = {
    script_hash = filemd5("${path.module}/deploy.sh")
  }
 
  provisioner "local-exec" {
    command = "./deploy.sh"
  }
}

Common Use Cases

#

Run Scripts After Resource Creation

#
resource "null_resource" "init_db" {
  triggers = {
    db_id = aws_db_instance.main.id
  }
 
  provisioner "local-exec" {
    command = "psql -h ${aws_db_instance.main.address} -U admin -f schema.sql"
    environment = {
      PGPASSWORD = var.db_password
    }
  }
 
  depends_on = [aws_db_instance.main]
}

Re-run When Files Change

#
resource "null_resource" "build" {
  triggers = {
    source_hash = sha256(join("", [
      filemd5("${path.module}/src/main.py"),
      filemd5("${path.module}/src/requirements.txt"),
    ]))
  }
 
  provisioner "local-exec" {
    command     = "cd src && pip install -r requirements.txt -t build/ && cp *.py build/"
    working_dir = path.module
  }
}

Remote Provisioning via SSH

#
resource "null_resource" "configure" {
  triggers = {
    instance_id = aws_instance.web.id
  }
 
  connection {
    type        = "ssh"
    user        = "ubuntu"
    private_key = file("~/.ssh/id_rsa")
    host        = aws_instance.web.public_ip
  }
 
  provisioner "file" {
    source      = "configs/nginx.conf"
    destination = "/tmp/nginx.conf"
  }
 
  provisioner "remote-exec" {
    inline = [
      "sudo mv /tmp/nginx.conf /etc/nginx/nginx.conf",
      "sudo systemctl restart nginx",
    ]
  }
}

Force Resource Recreation

#
resource "null_resource" "cluster_config" {
  triggers = {
    cluster_version = var.cluster_version
    config_hash     = filemd5("${path.module}/cluster-config.yaml")
    always_run      = timestamp()  # Runs every apply
  }
 
  provisioner "local-exec" {
    command = "kubectl apply -f cluster-config.yaml"
  }
}

terraform_data (Terraform 1.4+)

#

terraform_data is the built-in replacement for null_resource — no provider needed:

resource "terraform_data" "deploy" {
  triggers_replace = [
    filemd5("${path.module}/deploy.sh"),
    var.app_version,
  ]
 
  provisioner "local-exec" {
    command = "./deploy.sh ${var.app_version}"
  }
}
 
# Store and reference values
resource "terraform_data" "values" {
  input = {
    timestamp = timestamp()
    version   = var.app_version
  }
}
 
output "deploy_time" {
  value = terraform_data.values.output.timestamp
}

null_resource vs terraform_data

#
Featurenull_resourceterraform_data
Provider neededYes (hashicorp/null)No (built-in)
Available sinceAlwaysTerraform 1.4
Triggerstriggers maptriggers_replace list
Store valuesNoYes (input/output)
ProvisionersYesYes

When NOT to Use

#
  • Config management → Use Ansible, Chef, or cloud-init instead
  • Complex orchestration → Use CI/CD pipelines
  • API calls → Use a proper Terraform provider or http data source
#

Conclusion

#

Use terraform_data (Terraform 1.4+) instead of null_resource for new projects — it's built-in and can store values. Use triggers to control when provisioners re-run. Both are escape hatches — prefer native Terraform resources when possible.

#Terraform#DevOps#Infrastructure as Code

Share this article