Introduction
Terraform uses the depends_on meta-argument to handle hidden resource or module dependencies that it cannot automatically infer. This argument instructs Terraform to complete all actions on a dependency object, including read actions, before performing actions on the object declaring the dependency.
When you use depends_on with a data source, it can lead to unexpected behavior. If a resource that a data source depends_on changes, Terraform marks the data source to be read during the apply phase. Terraform does this because it cannot know the outcome of the resource change on the data source's values until after the change is applied. This can affect other resources that consume the data source's output.
This guide demonstrates two scenarios where using depends_on with a data source can produce an unexpected plan. The examples use the tfcoremock provider to simulate real-world resources and can be executed in any environment.
Prerequisites
- The Terraform CLI installed on your platform. You can find instructions in the Terraform installation guide.
Scenario 1: Unexpected Plan to Update a Resource
This scenario demonstrates how depends_on can cause Terraform to propose an update to a resource in the plan phase that does not occur during the apply phase.
Procedure
- Create a directory named
example1. -
Inside this directory, create a file named
main.tfwith the following content. This configuration defines a resourcehero1, a data sourceheroesthat depends on it, and a resourcehero2that uses the data source's output.terraform { required_providers { tfcoremock = { source = "hashicorp/tfcoremock" version = "0.2.0" } } } provider "tfcoremock" {} resource "tfcoremock_simple_resource" "hero1" { string = "Wonder Woman" } data "tfcoremock_simple_resource" "heroes" { id = "heroes_data" depends_on = [ tfcoremock_simple_resource.hero1 ] } resource "tfcoremock_simple_resource" "hero2" { string = data.tfcoremock_simple_resource.heroes.string } - Create a subdirectory named
terraform.data. -
Inside
terraform.data, create a file namedheroes_data.jsonwith the following content. This file provides the static data for theheroesdata source.{ "values": { "string": { "string": "Batman" } } } -
Confirm your directory structure matches the following.
. ├── main.tf └── terraform.data └── heroes_data.json -
Initialize the configuration.
$ terraform init
-
Apply the configuration to create the resources.
$ terraform apply
The output shows two resources created.
tfcoremock_simple_resource.hero1: Creating... tfcoremock_simple_resource.hero1: Creation complete after 0s [id=...] data.tfcoremock_simple_resource.heroes: Reading... data.tfcoremock_simple_resource.heroes: Read complete after 0s tfcoremock_simple_resource.hero2: Creating... tfcoremock_simple_resource.hero2: Creation complete after 0s [id=...] Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
-
Modify
main.tfto change the value ofhero1toSuperman.resource "tfcoremock_simple_resource" "hero1" { string = "Superman" } -
Run
terraform plan. Notice that the plan proposes changes to bothhero1andhero2. Becausehero2uses the data source output and the data sourcedepends_ona changed resource, Terraform marks its value as(known after apply).$ terraform plan
The output shows a plan to change two resources.
## data.tfcoremock_simple_resource.heroes will be read during apply ## (depends on a resource or a module with changes pending) <= data "tfcoremock_simple_resource" "heroes" { + bool = (known after apply) + float = (known after apply) + id = "heroes_data" + integer = (known after apply) + number = (known after apply) + string = (known after apply) } # tfcoremock_simple_resource.hero1 will be updated in-place ~ resource "tfcoremock_simple_resource" "hero1" { id = "..." ~ string = "Wonder Woman" -> "Superman" } # tfcoremock_simple_resource.hero2 will be updated in-place ~ resource "tfcoremock_simple_resource" "hero2" { id = "..." ~ string = "Batman" -> (known after apply) } Plan: 0 to add, 2 to change, 0 to destroy. -
Run
terraform apply. Notice that onlyhero1is modified. During the apply phase, Terraform re-reads theheroesdata source and finds its value (Batman) has not changed, so no update tohero2is necessary.$ terraform apply
The output shows only one resource was changed.
tfcoremock_simple_resource.hero1: Modifying... [id=...] tfcoremock_simple_resource.hero1: Modifications complete after 0s [id=...] data.tfcoremock_simple_resource.heroes: Reading... data.tfcoremock_simple_resource.heroes: Read complete after 0s Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
Scenario 2: Unexpected Plan to Replace a Resource
This scenario demonstrates how depends_on can cause Terraform to plan to destroy and re-create a resource, even when the underlying data does not change.
Procedure
- Create a directory named
example2. -
Inside this directory, create a file named
main.tfwith the following content. This configuration is similar to the first, but it uses the data source output to set theidattribute ofhero2.terraform { required_providers { tfcoremock = { source = "hashicorp/tfcoremock" version = "0.2.0" } } } provider "tfcoremock" {} resource "tfcoremock_simple_resource" "hero1" { string = "Wonder Woman" } data "tfcoremock_simple_resource" "heroes" { id = "heroes_data" depends_on = [ tfcoremock_simple_resource.hero1 ] } resource "tfcoremock_simple_resource" "hero2" { id = data.tfcoremock_simple_resource.heroes.string } - Follow steps 3-7 from Scenario 1 to create the
terraform.data/heroes_data.jsonfile and apply the configuration. -
Modify
main.tfto change the value ofhero1toSuperman.resource "tfcoremock_simple_resource" "hero1" { string = "Superman" } -
Run
terraform plan. Because theidattribute ofhero2is derived from the data source, and the data source's value is(known after apply), Terraform determines thathero2must be replaced.$ terraform plan
The output shows a plan to replace
hero2.## ... (output for data source and hero1) ... # tfcoremock_simple_resource.hero2 must be replaced -/+ resource "tfcoremock_simple_resource" "hero2" { ~ id = "Batman" -> (known after apply) # forces replacement } Plan: 1 to add, 1 to change, 1 to destroy. -
Run
terraform apply. Terraform destroyshero2before it re-reads the data source. After re-reading the data and confirming the value is stillBatman, it re-createshero2with the same ID.$ terraform apply
The output shows the resource was destroyed and re-created.
tfcoremock_simple_resource.hero2: Destroying... [id=Batman] tfcoremock_simple_resource.hero2: Destruction complete after 0s tfcoremock_simple_resource.hero1: Modifying... [id=...] tfcoremock_simple_resource.hero1: Modifications complete after 0s [id=...] data.tfcoremock_simple_resource.heroes: Reading... data.tfcoremock_simple_resource.heroes: Read complete after 0s tfcoremock_simple_resource.hero2: Creating... tfcoremock_simple_resource.hero2: Creation complete after 0s [id=Batman] Apply complete! Resources: 1 added, 1 changed, 1 destroyed.
Recommendation
In both scenarios, removing the depends_on meta-argument from the data source results in a more predictable plan. When a resource depends on the output of a data source, Terraform can infer the dependency automatically. Avoid using depends_on with data sources unless it is necessary to manage a hidden dependency that Terraform cannot detect, such as a dependency on a resource that modifies a file read by the local_file data source.
Additional Information
- For more information, refer to the Terraform
depends_onmeta-argument documentation. - You can find the Terraform Mock Provider Documentation on the Terraform Registry.
- To learn more about data sources, review the Data Sources Documentation.