Terraform is built on the principle of declarative infrastructure. You define the desired state, and Terraform figures out how to get there. However, sometimes you encounter situations where you need to introduce imperative logic, force resource recreation based on external changes, or simply have a placeholder for arbitrary data that drives other behaviors. For these scenarios, Terraform offers specialized tools like terraform_data
resource.
Introduced in Terraform v1.4, terraform_data
provides a more explicit and controlled way to manage arbitrary data and trigger resource replacement, often serving as a more refined alternative to null_resource
. This detailed guide will dive deep into terraform_data
, its core purpose, key arguments, and practical use cases, helping you master its capabilities for advanced dependency management and complex automation workflows.
What is the terraform_data Resource?
At its core, the terraform_data
resource stores an arbitrary value in the Terraform state and provides a mechanism to explicitly trigger its own replacement, which in turn can trigger the replacement of other resources.
Unlike most Terraform resources that map directly to an infrastructure object (like an EC2 instance or an S3 bucket), terraform_data
is a logical resource managed by the built-in terraform
provider. It does not create anything in your cloud provider; its value and utility lie solely within Terraform’s state and execution logic.
Why was terraform_data introduced?
Historically, the null_resource
was often used to fulfill roles like:
- Running provisioners that were not tied to a specific infrastructure resource.
- Serving as a “dummy” resource with
triggers
to force recreation when certain inputs changed.
While null_resource
works for these, its name and primary purpose (as a container for provisioners) did not perfectly align with the “arbitrary data/replacement trigger” use case. terraform_data
offers a cleaner, more semantically appropriate, and powerful solution for these specific patterns.
Key Arguments of terraform_data
The terraform_data
resource has a simple yet effective set of arguments:
input
(Required, Type:any
):- This is the primary argument. It accepts any value (string, number, list, map, object) that Terraform can serialize.
- The value provided to
input
is stored in the Terraform state and reflected in the resource’soutput
attribute. - Crucially: If the value of
input
changes betweenterraform apply
runs,terraform_data
will be marked for replacement.
triggers_replace
(Optional, Type:map(string)
):- This is a map of arbitrary string values.
- If any value in this map changes, it forces the
terraform_data
resource to be destroyed and recreated (i.e., replaced). - This is the powerful mechanism for explicit replacement logic, independent of the
input
value itself. - This argument serves a similar purpose to the
triggers
argument onnull_resource
, but its naming is more explicit about its intent.
How to Use terraform_data
: Practical Examples
Let us explore common scenarios where terraform_data
excels.
Use Case 1: Storing Arbitrary Data and Reacting to its Changes
The most basic use is to store a value and observe its lifecycle.
# main.tf
variable "config_version" {
description = "A version string to track configuration changes."
type = string
default = "v1.0"
}
resource "terraform_data" "app_config_tracker" {
input = var.config_version
# If config_version changes, this resource will be replaced.
# Its 'id' and 'output' will reflect the new input value.
}
output "tracked_config_version" {
description = "The version of the application configuration currently tracked."
value = terraform_data.app_config_tracker.output # The 'output' attribute reflects the 'input'
}
output "terraform_data_id" {
description = "The ID of the terraform_data resource (which is its input value)."
value = terraform_data.app_config_tracker.id # The 'id' attribute also reflects the 'input'
}
Explanation:
- When
var.config_version
is “v1.0”,terraform_data.app_config_tracker
will be created withinput = "v1.0"
. - If you then change
var.config_version
to “v1.1” and runterraform apply
, Terraform will detect a change interraform_data.app_config_tracker.input
. It will plan to destroy and recreate theterraform_data
resource.
Use Case 2: Forcing Resource Replacement with triggers_replace
This is where terraform_data
truly shines, especially for scenarios where you need to force the recreation of an infrastructure resource when a related, but not directly dependent, input changes.
Imagine you have an EC2 instance that needs to rebuild completely when a new application version is available, even if the AMI ID itself hasn’t changed.
# variables.tf
variable "app_version" {
description = "The application version to deploy."
type = string
default = "1.0.0"
}
variable "ami_id" {
description = "The AMI ID for the EC2 instance."
type = string
default = "ami-0abcdef1234567890" # Replace with a real AMI
}
# main.tf
resource "terraform_data" "app_deploy_trigger" {
# This resource will be replaced if the 'app_version' changes.
triggers_replace = {
version = var.app_version
}
}
resource "aws_instance" "web_server" {
ami = var.ami_id
instance_type = "t2.micro"
# The `replace_triggered_by` meta-argument tells Terraform
# to replace *this* resource if `terraform_data.app_deploy_trigger` is replaced.
lifecycle {
replace_triggered_by = [terraform_data.app_deploy_trigger]
}
tags = {
Name = "Web Server - ${var.app_version}"
AppVersion = var.app_version # Good practice to tag with the version
}
}
Workflow:
- Initial Deploy:
terraform apply -var="app_version=1.0.0"
terraform_data.app_deploy_trigger
is created.aws_instance.web_server
is created.
- New App Version:
terraform apply -var="app_version=1.1.0"
- Terraform detects
var.app_version
has changed. terraform_data.app_deploy_trigger
is marked for replacement because itstriggers_replace.version
changed.- Due to
lifecycle.replace_triggered_by
,aws_instance.web_server
is also marked for replacement (destroy and recreate). - This forces a clean redeployment of your web server with the new application version.
- Terraform detects
This pattern is incredibly useful for Blue/Green deployments, A/B testing, or simply ensuring that a fresh instance is launched whenever certain “logical” inputs change, even if the resource’s direct configuration remains the same.
Use Case 3: Combining with Provisioners for Post-Deployment Actions
While null_resource
is often used for provisioners, terraform_data
can also serve as a container, especially if the provisioner’s execution needs to be tied to a specific trigger.
# variables.tf
variable "configuration_script_hash" {
description = "Hash of a configuration script. Changing this will trigger instance re-provisioning."
type = string
default = "abc123def456"
}
resource "aws_instance" "app_server" {
ami = "ami-0abcdef1234567890"
instance_type = "t2.micro"
key_name = "my-ssh-key" # Ensure this key exists
}
resource "terraform_data" "instance_config_trigger" {
triggers_replace = {
# Force this resource to recreate (and thus re-run provisioners)
# if the instance ID changes OR if the script hash changes.
instance_id = aws_instance.app_server.id
script_hash = var.configuration_script_hash
}
# Now, attach the remote-exec provisioner to this terraform_data resource.
# It will run whenever 'instance_config_trigger' is replaced.
provisioner "remote-exec" {
inline = [
"echo 'Running configuration script for instance: ${self.triggers_replace.instance_id}'",
"curl -sL https://example.com/config_script.sh | bash -s -- ${self.triggers_replace.script_hash}"
]
connection {
type = "ssh"
user = "ec2-user"
private_key = file("~/.ssh/id_rsa")
host = aws_instance.app_server.public_ip
}
}
}
Explanation:
- The
remote-exec
provisioner is now tied to theterraform_data.instance_config_trigger
resource. - If
aws_instance.app_server.id
changes (instance replaced) orvar.configuration_script_hash
changes,instance_config_trigger
is replaced, and theremote-exec
provisioner runs again on the instance. - This is a robust way to ensure a configuration script runs whenever an instance is new or when the script logic itself is updated (by changing its hash).
terraform_data vs. null_resource: Which one to Choose?
This is the most common point of confusion. Here is a breakdown:
Feature | terraform_data | null_resource |
---|---|---|
Primary Intent | Arbitrary data storage, explicit replacement triggering. | Container for provisioners, “resource-like” behaviors. |
input Argument | Yes. Value stored in state, changes trigger replacement. | No direct input argument. |
Replacement Logic | triggers_replace (explicit). Changes to input also trigger. | triggers (explicit). Only changes to triggers values. |
ID in State | The input value. | A UUID (randomly generated). |
Best For | Forcing resource replacement, storing derived data. | Running provisioners that don’t need input or specific ID control. |
Clarity | More semantically clear for replacement logic. | Can be overloaded for various “dummy” resource patterns. |
When to choose terraform_data:
- You need to force another resource to replace based on a change in an input that is not directly configured on that resource.
- You want to store a specific computed value in the state that can then be referenced, and whose change should trigger logical actions (like a version string or a hash).
- You are attaching provisioners, and their re-execution should be explicitly tied to changes in arbitrary external values (via
triggers_replace
).
When to choose null_resource:
- You primarily need a container for
provisioners
that do not depend on complexinput
logic orid
specific to a custom value. - You are working with older Terraform versions (< v1.4) where
terraform_data
is not available.
In many modern scenarios where you would have used null_resource
with triggers
to force recreation, terraform_data
with triggers_replace
is often the more appropriate and clearer choice.
Advanced Considerations and Best Practices
- Idempotency of Provisioners: Remember that
terraform_data
only triggers provisioners. It is still your responsibility to ensure the commands executed by those provisioners are idempotent. - Do not Abuse It:
terraform_data
is powerful, but do not over-engineer simple dependencies. If a resource inherently depends on an input, make that dependency direct. - Lifecycle Rules: Understand
lifecycle
blocks, especiallyreplace_triggered_by
, to effectively control resource replacement behavior. - Clarity in
triggers_replace
: Use descriptive keys in yourtriggers_replace
map to indicate why a change will cause a replacement (e.g.,app_version
,config_checksum
). - Debugging: When
terraform_data
causes unexpected replacements, check theinput
value and thetriggers_replace
map in your plan output to see what changed.
Conclusion
The terraform_data
resource is a new addition to Terraform’s toolkit, offering granular control over resource lifecycles and enabling more complex automation patterns. By understanding its role in managing arbitrary data and its powerful triggers_replace
argument, you can design more robust, explicit, and adaptable Infrastructure as Code configurations.
While null_resource
still has its place, terraform_data
is often the preferred choice for scenarios demanding explicit replacement logic, pushing Terraform’s declarative power even further. Integrate it wisely into your workflows and elevate your Terraform mastery!
Author

Experienced Cloud & DevOps Engineer with hands-on experience in AWS, GCP, Terraform, Ansible, ELK, Docker, Git, GitLab, Python, PowerShell, Shell, and theoretical knowledge on Azure, Kubernetes & Jenkins. In my free time, I write blogs on ckdbtech.com