Terraform depends_on Meta Argument Explained

Published:
October 14, 2026
Topics:

Terraform automatically determines most resource dependencies from references between resources. However, some infrastructure dependencies are not expressed through resource attributes, so Terraform cannot infer them automatically. The depends_on meta-argument lets you explicitly define these dependencies and control the order in which Terraform processes resources and modules.

This article explains how the Terraform depends_on meta-argument works, how it differs from implicit dependencies, and how to use it with phoenixNAP Bare Metal Cloud infrastructure.

Terraform depends_on meta argument explained.

What Is the depends_on Meta-Argument?

The depends_on meta-argument explicitly tells Terraform that one resource or module depends on another resource or module. Terraform processes the dependency before the object that declares it, even when the configuration contains no other reference that would let Terraform infer the relationship automatically.

Terraform supports depends_on in resource and module blocks. The argument accepts a list of resource or module references in the same calling module.

For example, the following configuration creates an explicit dependency between two resources:

resource "example_resource" "database" {
  # Database configuration
}

resource "example_resource" "application" {
  depends_on = [
    example_resource.database
  ]

  # Application configuration
}

Terraform completes the relevant operations for example_resource.database before processing example_resource.application.

The dependency also affects how Terraform processes the infrastructure during other operations, including reads and destroys. Therefore, depends_on represents an actual dependency in Terraform's dependency graph rather than simply adding a delay between two API calls.

Note: Learn more about managing infrastructure declaratively with Terraform using our Infrastructure as Code resources.

How Does depends_on Meta-Argument Work?

Terraform normally determines dependencies by analyzing expressions in the configuration. When one resource uses an attribute from another, Terraform automatically creates a dependency between them. depends_on provides an alternative when the relationship exists but Terraform cannot discover it from the configuration.

Consider two generic resources:

resource "example_resource" "network" {
  # Network configuration
}

resource "example_resource" "server" {
  depends_on = [
    example_resource.network
  ]

  # Server configuration
}

The depends_on argument adds an explicit edge from example_resource.network to example_resource.server in Terraform's dependency graph. Terraform therefore waits for the network resource to complete its required operations before processing the server.

The argument does not specify an arbitrary delay or force Terraform to execute every operation sequentially. Terraform still executes independent operations in parallel. depends_on only adds the dependency relationship that Terraform needs to respect.

This distinction is important with BMC infrastructure. For example, if a BMC server references a private network ID, Terraform already knows that the network must be created first. Adding depends_on to the server would duplicate information that Terraform already has.

Understanding Implicit vs. Explicit Dependencies in Terraform

Terraform uses two primary mechanisms to determine resource ordering: implicit dependencies and explicit dependencies. Implicit dependencies come from references between resources, while explicit dependencies come from depends_on.

For example, this phoenixNAP configuration creates an implicit dependency:

resource "pnap_private_network" "internal" {
  name     = "internal-network"
  cidr     = "10.0.0.0/24"
  location = "PHX"
}

resource "pnap_server" "worker" {
  hostname    = "worker-01"
  os          = "ubuntu/jammy"
  type        = "s2.c1.medium"
  location    = "PHX"
  network_type = "PRIVATE_ONLY"

  network_configuration {
    private_network_configuration {
      configuration_type = "USER_DEFINED"

      private_networks {
        server_private_network {
          id  = pnap_private_network.internal.id
          ips = ["10.0.0.15"]
        }
      }
    }
  }
}

The pnap_server.worker resource references pnap_private_network.internal.id. Terraform therefore knows that it must provision the private network before the server. No depends_on argument is necessary.

An explicit dependency is useful when this type of reference does not exist. For example:

resource "example_resource" "network" {
  # Configuration
}

resource "example_resource" "server" {
  depends_on = [
    example_resource.network
  ]

  # Configuration
}

Here, the server depends on the network, but its configuration does not reference any network attribute. Terraform cannot infer the relationship, so depends_on makes it explicit.

As a rule, prefer implicit dependencies when a resource reference can express the relationship. Use depends_on for dependencies that Terraform cannot otherwise infer. HashiCorp recommends explicit dependencies only as a last resort because they can lead to more conservative plans. The following diagram illustrates the difference between implicit and explicit dependencies in Terraform and shows how each approach establishes a dependency between resources:

Implicit vs. explicit dependency in Terraform.

Syntax and Placement Rules Within Resource and Module Blocks

The depends_on argument goes directly inside the resource or module block that depends on another resource or module. Its value is a list containing references to resources or child modules.

The basic resource syntax is:

resource "resource_type" "resource_name" {
  depends_on = [
    resource_type.dependency
  ]

  # Other arguments
}

You can specify multiple dependencies in the same list:

resource "example_resource" "application" {
  depends_on = [
    example_resource.database,
    example_resource.network,
    example_resource.storage
  ]

  # Configuration
}

Terraform waits for all specified dependencies before processing the dependent resource.

You can also use depends_on in a module block:

module "network" {
  source = "./modules/network"
}

module "compute" {
  source = "./modules/compute"

  depends_on = [
    module.network
  ]
}

The references in depends_on must identify resources or child modules in the same calling module. Terraform must know these relationships while constructing the dependency graph, so depends_on does not accept arbitrary expressions.

For phoenixNAP BMC configurations, a resource-level dependency is generally preferable when only one server or resource requires the ordering constraint. A module-level dependency is more appropriate when the entire compute module depends on the network module.

How Terraform Constructs the Resource Dependency Graph (DAG)

Terraform represents infrastructure as a dependency graph, which determines how it processes resources during operations such as planning, refreshing, creating, updating, and destroying infrastructure. Each resource becomes a node in the graph, while dependencies create relationships between those nodes.

For example, a BMC configuration with a private network and server can produce a relationship similar to the following:

Terraform dependency example.

Terraform detects this relationship automatically when the server references the private network:

id = pnap_private_network.internal.id

An explicit depends_on relationship produces the same type of dependency edge:

resource "pnap_server" "worker" {
  depends_on = [
    pnap_private_network.internal
  ]

  # Configuration
}

Terraform adds explicit dependencies to the graph before it evaluates other dependency relationships. It then uses the resulting graph to determine which operations can run concurrently and which must wait for another resource.

The graph must remain acyclic. If resources depend on each other in a circular chain, Terraform cannot determine a valid execution order and reports a cycle error. This becomes particularly important when several depends_on declarations are combined across resources or modules.

The following diagram illustrates how Terraform builds a resource dependency graph (DAG) to determine resource execution order and identify resources that can be processed in parallel:

Diagram showing dependent vs. independent resources.

Working with Implicit and Explicit Dependencies

Terraform automatically creates dependencies when resource configurations reference attributes from other resources. This behavior usually provides the most precise dependency relationship because Terraform can identify exactly which resource must exist before another resource can use it. Explicit dependencies supplement this behavior when a relationship exists outside the references Terraform can detect.

When Implicit Dependencies Automatically Handle Resource Ordering

An implicit dependency exists when one resource references another resource's attributes. Terraform analyzes these references and adds the required dependency to the resource graph without requiring a depends_on declaration.

For example, a phoenixNAP private network can be created before a BMC server that uses the network's ID:

resource "pnap_private_network" "internal" {
  name     = "internal-network"
  cidr     = "10.0.0.0/24"
  location = "PHX"
}

resource "pnap_server" "worker" {
  hostname    = "worker-01"
  os          = "ubuntu/jammy"
  type        = "s2.c1.medium"
  location    = "PHX"
  network_type = "PRIVATE_ONLY"

  network_configuration {
    private_network_configuration {
      configuration_type = "USER_DEFINED"

      private_networks {
        server_private_network {
          id  = pnap_private_network.internal.id
          ips = ["10.0.0.15"]
        }
      }
    }
  }
}

The id = pnap_private_network.internal.id reference creates the dependency. Terraform therefore knows that pnap_private_network.internal must be available before it can provision pnap_server.worker.

In this situation, adding the following to the server is unnecessary:

depends_on = [
  pnap_private_network.internal
]

The existing reference already communicates the dependency. Relying on the implicit relationship also gives Terraform more information about the specific value being used, which can produce a more precise dependency graph.

Identifying Scenarios Where Implicit Dependencies Fail

Implicit dependencies cannot represent relationships that do not appear as references in the Terraform configuration. This can happen when one resource must be provisioned first because of an operational requirement, even though the dependent resource does not consume any of its attributes.

For example, consider a configuration where an application server must wait for a separate initialization resource:

resource "example_resource" "initialization" {
  # Performs an initialization operation
}

resource "example_resource" "application" {
  # Does not reference the initialization resource
}

Terraform has no expression that connects the two resources, so it treats them as independent. It may therefore process them concurrently.

Adding an explicit dependency makes the operational relationship visible:

resource "example_resource" "application" {
  depends_on = [
    example_resource.initialization
  ]

  # Configuration
}

This pattern is useful when a dependency represents an ordering requirement rather than a data relationship. Examples include waiting for an infrastructure component to finish initializing, ensuring a prerequisite configuration exists, or coordinating resources whose APIs don't expose a direct attribute reference.

The key question is whether Terraform can already determine the dependency from the configuration. If it can, use the reference. If it cannot, consider depends_on.

Using depends_on to Enforce Strict Execution Order Across Resources

The depends_on meta-argument adds an explicit dependency when Terraform cannot infer the relationship automatically. Terraform then ensures that the dependency's required operations complete before it processes the dependent resource.

For example:

resource "example_resource" "network_setup" {
  # Network setup
}

resource "example_resource" "server_configuration" {
  depends_on = [
    example_resource.network_setup
  ]

  # Server configuration
}

Without depends_on, Terraform sees no relationship between the two resources and can potentially process them at the same time. With the explicit dependency, Terraform adds the relationship to its dependency graph:

Terraform explicit dependency example.

This does not turn Terraform into a completely sequential system. Terraform still processes resources concurrently when no dependency exists between them. The explicit dependency only constrains the resources connected by that relationship.

This distinction matters when managing larger infrastructures. Unnecessary dependencies can reduce Terraform's ability to run operations in parallel and make plans more conservative than needed. Use depends_on to represent a real dependency rather than to impose an arbitrary execution sequence.

Scoping Dependencies: Resource-Level vs. Module-Level depends_on

The scope of an explicit dependency determines how broadly Terraform applies the ordering constraint. A resource-level depends_on affects one resource, while a module-level dependency can affect the resources and data sources managed by the dependent module.

A resource-level dependency is appropriate when only one resource requires another resource to complete first:

resource "pnap_server" "worker" {
  depends_on = [
    pnap_private_network.internal
  ]

  # Server configuration
}

A module-level dependency applies the relationship to an entire child module:

module "network" {
  source = "./modules/network"
}

module "compute" {
  source = "./modules/compute"

  depends_on = [
    module.network
  ]
}

In this example, Terraform treats the compute module as dependent on the network module. The dependency therefore has a broader scope than a depends_on declaration on a single server.

For this reason, prefer resource-level dependencies when possible. Use a module-level depends_on only when the dependency genuinely applies to the module as a whole. A broad module dependency can affect otherwise unrelated resources and make Terraform's plans and execution order more conservative.

depends_on Examples with Bare Metal Cloud (BMC)

phoenixNAP Bare Metal Cloud provides several infrastructure components that Terraform can manage independently, including bare metal servers, private networks, IP blocks, and public networks. These resources can have operational dependencies even when their Terraform configurations do not directly reference one another.

Note: phoenixNAP Bare Metal Cloud provides API-driven bare metal infrastructure that you can provision and manage with Terraform. Explore Bare Metal Cloud to learn more.

Enforcing Private Network Provisioning Before Bare Metal Server Deployment

A private network often provides the connectivity that a BMC server requires after provisioning. When the server configuration references the private network's ID, Terraform automatically creates an implicit dependency and provisions the network first.

For example:

resource "pnap_private_network" "backend" {
  name     = "backend-network"
  cidr     = "10.0.0.0/24"
  location = "PHX"
}

resource "pnap_server" "app" {
  hostname     = "app-server"
  location     = "PHX"
  type         = "s1.c1.small"
  os           = "ubuntu/jammy"
  network_type = "PRIVATE_ONLY"

  network_configuration {
    private_network_configuration {
      configuration_type = "USER_DEFINED"

      private_networks {
        server_private_network {
          id  = pnap_private_network.backend.id
          ips = ["10.0.0.10"]
        }
      }
    }
  }
}

The pnap_server.app resource references pnap_private_network.backend.id, so Terraform knows that the private network must be created before the server. An explicit depends_on would therefore be redundant.

However, consider a server that does not consume any attribute from the private network but still requires the network to be provisioned first because of an external operational requirement:

resource "pnap_private_network" "backend" {
  name     = "backend-network"
  cidr     = "10.0.0.0/24"
  location = "PHX"
}

resource "pnap_server" "gateway" {
  hostname = "gateway-node"
  location = "PHX"
  type     = "s1.c1.small"
  os       = "ubuntu/jammy"

  depends_on = [
    pnap_private_network.backend
  ]
}

Here, depends_on explicitly adds the missing relationship. phoenixNAP's Terraform documentation uses this same pattern to demonstrate an explicit dependency between a private network and a BMC server when no resource attribute reference exists.

Sequencing Bare Metal Server Deployment After Storage Volume Attachment

Storage introduces another type of dependency: a server workflow can depend on storage infrastructure being ready even when the server configuration does not directly reference the storage resource. In such cases, depends_on can express the required Terraform ordering.

For example, if a configuration manages a storage-related resource separately from a server, an explicit dependency can ensure that the storage operation completes first:

resource "example_storage_resource" "application" {
  # Storage configuration
}

resource "pnap_server" "application" {
  hostname = "storage-app"
  location = "PHX"
  type     = "s1.c1.medium"
  os       = "ubuntu/jammy"

  depends_on = [
    example_storage_resource.application
  ]
}

The important distinction is that depends_on controls Terraform's dependency graph. It does not automatically attach a storage volume to a BMC server or create a storage relationship that the phoenixNAP API does not define.

Use this pattern when Terraform manages a real prerequisite separately and the server must wait for it to complete. If the server configuration directly references a storage resource attribute, use that reference instead and let Terraform create the implicit dependency.

Delaying Bare Metal Instance Startup Until Out-of-Band IPMI/BMC Access is Configured

Out-of-band management introduces dependencies that may exist outside a server's standard Terraform attributes. For example, an infrastructure workflow may require an external configuration step to complete before subsequent server configuration can proceed.

Terraform can model that relationship when the prerequisite is represented by a managed resource:

resource "terraform_data" "bmc_access_setup" {
  input = "configure-bmc-access"
}

resource "pnap_server" "application" {
  hostname = "application-server"
  location = "PHX"
  type     = "s1.c1.medium"
  os       = "ubuntu/jammy"

  depends_on = [
    terraform_data.bmc_access_setup
  ]
}

The terraform_data resource represents the prerequisite inside Terraform's dependency graph. The server waits for that resource before Terraform processes the server operation.

This pattern is useful when an out-of-band process is part of the infrastructure workflow, but it should not be interpreted as a requirement of the phoenixNAP BMC API itself. The explicit dependency models the workflow dependency in Terraform.

Ensuring VLAN and NIC Bonding Prerequisites Complete Before Host Configuration

Network configuration can also contain operational dependencies that Terraform cannot infer from unrelated resources. phoenixNAP BMC exposes private-network details such as VLAN information through the server's network configuration, while public networks can also have an explicitly assigned VLAN.

For example, a host-configuration step can depend on a separately managed network preparation step:

resource "pnap_private_network" "application" {
  name     = "application-network"
  cidr     = "10.10.0.0/24"
  location = "PHX"
}

resource "terraform_data" "network_configuration" {
  input = pnap_private_network.application.id
}

resource "terraform_data" "host_configuration" {
  depends_on = [
    terraform_data.network_configuration
  ]

  input = "configure-host-networking"
}

The dependency chain becomes:

Terraform dependency flowchart.

This pattern is useful when Terraform manages both infrastructure provisioning and subsequent configuration steps. The important point is to model only dependencies that actually exist. If a server directly references the private network ID, Terraform already understands that relationship and does not need another depends_on declaration.

Managing Module Dependencies Between BMC Network Core and Compute Pools

Larger BMC deployments often separate infrastructure into Terraform modules. For example, one module can manage networking while another provisions a group of servers. When the compute module consumes an output from the network module, Terraform automatically creates an implicit module dependency.

For example:

module "network" {
  source = "./modules/network"
}

module "compute" {
  source = "./modules/compute"

  network_id = module.network.network_id
}

The network_id = module.network.network_id reference tells Terraform that the compute module depends on the network module. No explicit depends_on is required.

A module-level dependency becomes useful when the compute module must wait for the network module even though it does not consume one of its outputs:

module "network" {
  source = "./modules/network"
}

module "compute" {
  source = "./modules/compute"

  depends_on = [
    module.network
  ]
}

Terraform then treats the entire compute module as dependent on the network module. This approach can help when the dependency reflects an operational relationship rather than a value passed between modules. However, it is broader than a direct output reference and can affect multiple resources inside the dependent module. phoenixNAP's Terraform guidance similarly recommends direct module output references when a real data dependency exists, reserving module-level depends_on for relationships that cannot be expressed that way.

Troubleshooting depends_on Issues

Explicit dependencies help Terraform model relationships that it cannot infer automatically, but incorrect or overly broad dependencies can also make the dependency graph harder to manage. Common problems include circular dependencies, unexpected destroy behavior, and unnecessary resource changes caused by module-level dependencies.

The following troubleshooting techniques help identify the source of depends_on issues and determine whether an explicit dependency is necessary.

Resolving "Cycle Error" Failures in Terraform Graph Construction

Terraform requires an acyclic dependency graph so it can determine a valid order for processing resources. A cycle occurs when a chain of dependencies eventually points back to a resource earlier in the same chain.

For example, the following configuration creates a circular dependency:

resource "example_resource" "network" {
  depends_on = [
    example_resource.server
  ]
}

resource "example_resource" "server" {
  depends_on = [
    example_resource.network
  ]
}

Terraform cannot determine which resource to process first because each resource waits for the other. The plan therefore fails with a cycle error. A longer dependency chain can create the same problem:

Terraform dependency cycle example.

To resolve the error, trace the dependency chain and remove any relationship that isn't a genuine prerequisite. In particular, check recently added depends_on declarations and module-level dependencies, which can introduce indirect cycles that are less obvious from an individual resource block.

When possible, replace unnecessary explicit dependencies with direct resource references. A reference gives Terraform the dependency information it needs without adding an additional relationship to the graph.

Debugging Missing Reference Dependencies in Destroy Operations

Dependencies affect resource destruction as well as creation. Terraform uses the dependency graph to determine the order in which it removes infrastructure, so an explicit dependency can keep a resource until another resource is destroyed or otherwise processed.

For example:

resource "example_resource" "network" {
  # Network configuration
}

resource "example_resource" "server" {
  depends_on = [
    example_resource.network
  ]

  # Server configuration
}

Terraform understands that the server depends on the network. During destruction, it reverses the dependency relationship so that the dependent resource is removed before its prerequisite:

Create:

network → server

Destroy:

server → network

Unexpected destroy behavior can occur when a dependency is broader than intended. A module-level depends_on, for example, can cause Terraform to treat resources inside an entire module as dependent on another module.

When investigating a destroy operation, inspect both the resource's direct references and its explicit dependencies. Removing an unnecessary depends_on can make the resulting graph more precise and prevent unrelated resources from being coupled during lifecycle operations.

Inspecting Dependency Chains with terraform graph and Graphviz

Terraform provides the terraform graph command to inspect the dependency graph generated from the current configuration. The command outputs the graph in DOT format, which can be difficult to read directly for larger configurations.

Run:

terraform graph
terraform graph command output.

The output contains the nodes and relationships Terraform uses to represent the configuration. You can pipe this output to Graphviz to generate a visual representation:

terraform graph | dot -Tsvg > graph.svg

The resulting SVG file makes longer dependency chains easier to inspect. For example, here is a simplified graph generated by Graphviz:

Example of Graphviz generated graph.

This is particularly useful when a configuration combines implicit references, resource-level depends_on, and module-level dependencies. If Terraform reports a cycle or produces an unexpected ordering, inspect the graph to identify which relationship creates the problematic path.

Note: Install Graphviz separately if the dot command is not available on the system. The terraform graph command itself generates the dependency information, while Graphviz only renders that information into a visual format.

Fixing Unexpected Resource Recreation Triggered by Module-Level depends_on

A module-level depends_on can have a broader effect than expected because Terraform treats the entire module as dependent on the specified object. Changes in the upstream object can therefore affect how Terraform evaluates resources in the dependent module.

For example:

module "network" {
  source = "./modules/network"
}

module "compute" {
  source = "./modules/compute"

  depends_on = [
    module.network
  ]
}

The configuration tells Terraform that the entire compute module depends on module.network. This can be unnecessarily restrictive when only one resource inside the compute module actually requires the network module to complete first.

A more precise design passes the required value through a module output:

module "network" {
  source = "./modules/network"
}

module "compute" {
  source = "./modules/compute"

  network_id = module.network.network_id
}

Terraform can then infer the dependency from the module.network.network_id reference.

Broad depends_on declarations can also make Terraform more conservative because it must account for the possibility that changes to the dependency affect the dependent object. If a plan unexpectedly proposes resource updates or replacements after an unrelated change, inspect module-level dependencies first.

The preferred solution is to replace a broad dependency with a specific resource or output reference whenever the infrastructure relationship can be expressed directly. This keeps the dependency graph smaller and gives Terraform more information about the actual relationship between resources.

Best Practices for Using depends_on

The depends_on meta-argument is most effective when it represents a dependency that Terraform cannot determine from the configuration itself. Overusing it can make the dependency graph unnecessarily restrictive, reduce parallelism, and produce less predictable plans.

Follow these practices to keep dependencies explicit without making the configuration more complex than necessary:

  • Prefer implicit dependencies whenever possible. Reference resource attributes directly when one resource consumes another resource's values. Let Terraform infer the dependency instead of adding depends_on unnecessarily.
  • Use depends_on for hidden dependencies. Add an explicit dependency when an operational or sequencing requirement exists, but no resource reference expresses it.
  • Keep dependencies as narrow as possible. Apply depends_on to an individual resource when only that resource requires the dependency.
  • Avoid unnecessary module-level dependencies. Use module-level depends_on only when the entire module depends on another module. Prefer module outputs when a specific value creates the dependency.
  • Do not use depends_on to create arbitrary delays. The argument defines a dependency relationship; it does not provide a configurable waiting period between operations.
  • Avoid redundant dependencies. If a resource already references another resource, adding the same resource to depends_on adds no additional dependency information.
  • Check dependencies when Terraform reports a cycle. Trace both explicit depends_on declarations and implicit resource references to find the circular relationship.
  • Review broad dependencies when plans become unexpected. A module-level dependency can make Terraform's plans more conservative and may contribute to unexpected updates or replacements.
  • Use terraform graph for complex configurations. Visualizing the dependency graph can help identify indirect dependencies and problematic chains.
  • Verify the resulting plan before applying changes. Use terraform plan to confirm that Terraform processes resources in the intended order and does not introduce unrelated changes.

Conclusion

This article explained how Terraform uses implicit and explicit dependencies, how the depends_on meta-argument affects the dependency graph, and how to apply it to phoenixNAP Bare Metal Cloud infrastructure. Use depends_on when Terraform cannot infer a required dependency from resource references.

For further information, see the phoenixNAP guides on Terraform configuration and Terraform modules.

Was this article helpful?
YesNo