terraform_data and Triggers
Scenario: A deployment needs a small integration step - say, notifying a downstream system - to rerun specifically when a release's checksum changes, but there's no actual provider-managed resource that naturally represents "a release happened."
The built-in terraform_data resource (which ships with Terraform itself, needing no external provider at all) exists to solve exactly this shape of problem: it holds arbitrary values and lifecycle triggers without corresponding to any real infrastructure object. Its most useful argument, triggers_replace, lets you declare "replace this resource - and, if absolutely necessary, re-run any provisioner attached to it - whenever this computed value changes," which is precisely how you'd model "rerun this step when the release checksum changes" without inventing a fake resource just to hang that logic on.
terraform_data is a genuine improvement over the older, now-legacy null_resource pattern specifically in how it participates in the dependency graph - it behaves more predictably as a real graph node other resources can depend on. But it's worth being honest about its limits: it does not turn an attached provisioner's arbitrary script into something declarative or independently observable - all the previous section's cautions about opaque scripts still apply in full to whatever provisioner you attach here. terraform_data solves "when should this trigger," not "what does the triggered script actually do, and can Terraform verify it worked."
Analogy: A terraform_data trigger is a dated work order pinned to a contractor's schedule - it precisely records when the contractor should be called out. It says nothing at all about what actually happened once the contractor's van pulled away and the work began; that part remains just as opaque as any other provisioner.
A worked configuration
resource "terraform_data" "release_hook" {
triggers_replace = [filesha256("release.json")]
provisioner "local-exec" { command = "./publish-release.sh" }
}
Whenever the operation you're modeling has a genuine, durable lifecycle of its own - something that should be created once, tracked, and potentially rolled back - prefer a real provider resource or a dedicated CI pipeline step over bolting it onto a terraform_data trigger. A strong warning sign that an integration has outgrown this pattern: if recovering from a failure ever requires manually editing Terraform state to get back to a working plan, that operation almost certainly belongs somewhere other than inside Terraform.
Common mistakes in production
- Using
terraform_datawith an attached provisioner as a permanent substitute for a real provider resource or a CI pipeline step, for something that genuinely has its own durable lifecycle. - Assuming
terraform_datamakes an attached script's effects observable or declarative - it only controls when replacement is triggered, not what the script does or whether it succeeded in a way Terraform can verify.
Goal: Treat the plan as a change contract: understand every create, update, replacement, and destroy before approving it. Reinforce this lesson in the tf-terraform-data-triggers Terraform lab. Open/labs/terraformand choose slugtf-terraform-data-triggers; the lab runs real Terraform against the offline FakeCloud provider.