Terraform depends_on: When and Why

Khimananda Oli 9 min read Virtualization
Terraform depends_on: When and Why

By Khimananda Oli | Last reviewed: August 2026

Terraform depends_on is the explicit mechanism for declaring resource ordering when implicit attribute references cannot capture a real-world dependency. Most engineers overuse it, creating brittle graphs that fail during updates or destroy operations because they misunderstand how Terraform resolves state. Understanding Terraform depends_on: When and Why requires distinguishing between data dependencies (which Terraform handles automatically) and side-effect dependencies (which require manual intervention). This guide covers the precise scenarios where explicit dependencies are mandatory versus harmful.

Implicit vs Explicit Dependency ResolutionResource Aaws_db_instance.mainResource Baws_app_server.webendpoint = aws_db_instance.main.endpointIMPLICIT: Auto-ordered + Data PassedResource Cnull_resource.setupResource Dhelm_release.appdepends_on = [null_resource.setup]EXPLICIT: Ordering Only (No Data)
Implicit references pass data and enforce order automatically; explicit Terraform depends_on enforces order without data coupling.

How does Terraform depends_on differ from implicit references?

Terraform builds a directed acyclic graph (DAG) of all resources before executing any API calls. When you reference an attribute like aws_db_instance.main.endpoint inside another resource, Terraform infers two things simultaneously: the target must be created first, and its output value is required for configuration. This is an implicit dependency. It is resilient because if you later remove the reference, the ordering constraint vanishes with it.

Explicit depends_on creates an edge in the DAG without transferring data. You are telling Terraform "create this after that" purely for temporal reasons. The danger arises when engineers use depends_on as a substitute for proper attribute references. If Resource B actually needs Resource A's ID but you only declare depends_on, Terraform will create A first, then B, but B receives no value. During updates, if A changes in a way that doesn't trigger recreation but alters runtime behavior, B won't know to update. This decoupling of configuration from ordering is the primary source of drift in mature infrastructure as code with Terraform projects.

In practice, implicit references also allow greater parallelism. Terraform can evaluate independent branches of the graph concurrently. An unnecessary depends_on serializes operations that could otherwise run in parallel, increasing apply times significantly in large state files. Always prefer referencing an actual attribute, even if you have to use a dummy output or a null_data_source to make the dependency visible to the graph.

When should you use Terraform depends_on for side effects?

The legitimate use case for explicit dependencies involves resources where the API returns "created" before the service is actually usable. Cloud providers are eventually consistent. A database instance may report ACTIVE status while still initializing internal schemas, rejecting connections for 30–90 seconds afterward. A Kubernetes namespace may exist in etcd before the admission controller is ready to accept pods. These are side-effect dependencies: the state says "done," but reality says "wait."

Handling eventual consistency in cloud APIs

Consider deploying a Helm chart into an EKS cluster immediately after creating the cluster. The aws_eks_cluster resource completes when the control plane is provisioned, but the OIDC provider and API server endpoint may take additional time to stabilize. Without explicit ordering, your Helm release fails with authentication errors.

resource "aws_eks_cluster" "main" {
  name     = var.cluster_name
  role_arn = aws_iam_role.eks.arn

  vpc_config {
    subnet_ids = var.subnet_ids
  }
}

# Wait for cluster endpoint to be fully operational
resource "time_sleep" "wait_for_eks" {
  depends_on      = [aws_eks_cluster.main]
  create_duration = "60s"
}

resource "helm_release" "app" {
  name       = "my-app"
  repository = "https://charts.example.com"
  chart      = "my-app"
  namespace  = "default"

  # Explicit dependency on the sleep, not directly on the cluster
  depends_on = [time_sleep.wait_for_eks]
}

Note the pattern here: we depend on a time_sleep resource, not directly on the EKS cluster. This isolates the workaround. If AWS fixes their API responsiveness, you delete the sleep block and the Helm release naturally reverts to depending only on what it needs. Direct depends_on on the cluster would permanently couple these resources even after the underlying issue is resolved.

Managing external system integrations

Another valid scenario involves resources outside Terraform's management. If you provision an S3 bucket and then need to configure a third-party CDN that reads from that bucket via a separate API call wrapped in a null_resource provisioner, Terraform has no visibility into the CDN's readiness. The null_resource must explicitly depend on the bucket, and subsequent resources consuming the CDN must depend on the null_resource. This chain makes the invisible visible. For teams managing complex hybrid environments, understanding these boundaries is critical, much like distinguishing between Terraform vs Ansible responsibilities.

Safe Pattern: Isolating Eventual Consistency WorkaroundsCloud ResourceAPI Returns ACTIVE(But Not Ready)time_sleepcreate_duration = 60sIsolated WorkaroundDependent ResourceHelm / Config / AppActually Works NowANTI-PATTERN: Direct depends_on on Cloud ResourceCouples workaround permanently; harder to remove when API improves
Isolating waits through time_sleep keeps the dependency graph clean and removable when provider APIs improve.

Why do modules require special depends_on handling?

Modules introduce a boundary that complicates dependency resolution. Inside a module, you can reference resources normally. But when calling a module, you cannot pass individual resource attributes as dependencies unless the module exposes them as outputs. This leads to a common anti-pattern: adding depends_on at the module call site because the module doesn't export what you need.

The correct approach is to design modules with explicit outputs that represent completion signals. If a networking module creates VPCs, subnets, NAT gateways, and route tables, expose a vpc_id or a dedicated network_ready output. Consuming modules reference this output, creating an implicit dependency on the entire network stack. This is superior to depends_on = [module.networking] because it documents what is being waited for, not just that something is being waited for.

There is one exception: when a module performs side effects that aren't captured by any output. For example, a module that creates an IAM role and attaches policies might complete before policy propagation finishes across AWS regions. In this case, the module should internally use time_sleep and expose a role_arn output that implicitly depends on the sleep. Callers then get correct ordering without knowing about the workaround. This encapsulation is key to maintainable Terraform modules for reusable infrastructure.

What are the risks of misusing explicit dependencies?

Overusing depends_on creates three categories of problems that compound over time. First, destroy-order failures. Terraform destroys resources in reverse dependency order. If you've created artificial chains, destruction may attempt to delete a resource while something still logically depends on it, even though Terraform's graph says otherwise. This is especially painful with databases and storage buckets that refuse deletion while connections exist.

Second, reduced parallelism. Each explicit edge serializes operations. In a state file with 500 resources, unnecessary dependencies can double or triple apply times. Teams often blame Terraform performance when the real issue is self-inflicted graph constraints. Profile your applies with terraform plan -parallelism=10 and examine the graph visualization (terraform graph | dot -Tpng > graph.png) to identify bottlenecks.

Third, maintenance burden. Explicit dependencies are opaque. Six months later, no one remembers why depends_on = [aws_lambda_function.processor] exists. Was it a timing issue? A data dependency someone forgot to wire? A leftover from debugging? Comments help, but comments rot. Implicit references are self-documenting: the attribute name explains the relationship. When refactoring, implicit dependencies move with the code; explicit ones become dangling references that cause confusing errors.

ScenarioUse Implicit ReferenceUse Explicit depends_onRisk Level
Resource needs another's ID/ARN/endpoint✅ Yes❌ NoLow
API returns ready before service accepts requests❌ No✅ Yes (via time_sleep)Medium
Module completion signal needed✅ Via output⚠️ Only if no output existsMedium
External system configured via provisioner❌ No✅ YesHigh
"Just to be safe" ordering❌ No❌ NoCritical
Decision Framework: Implicit vs Explicit DependenciesDoes Resource Need Data?YESNOUse Implicit ReferenceIs It a Side Effect?YESNOUse depends_on + time_sleepRe-evaluate DesignSide effects: API eventual consistency, external systems, provisioner timingData needs: IDs, endpoints, ARNs, connection strings, config values
Decision framework for Terraform depends_on: data dependencies use implicit references; side effects may justify explicit ordering.

How do you debug dependency issues in Terraform plans?

When applies fail due to ordering, resist the urge to add depends_on immediately. First, generate the dependency graph: terraform graph -type=plan | dot -Tsvg > plan.svg. Visual inspection reveals whether the missing edge is truly absent or whether the failure stems from something else entirely, like insufficient IAM permissions masquerading as a timing issue.

Use terraform console to test attribute availability. If aws_db_instance.main.endpoint returns a value in the console but fails during apply, you're dealing with eventual consistency. If it returns null or errors, you have a genuine missing dependency. Check provider documentation for known async behaviors; many AWS and Azure resources have documented stabilization periods that should inform your time_sleep durations.

For module boundaries, run terraform plan -out=tfplan && terraform show -json tfplan | jq '.resource_changes[] | select(.module_address != null)' to inspect which module resources are changing and in what order. This JSON output is more reliable than visual graph parsing for large states. Remember that depends_on inside modules affects only resources within that module; cross-module dependencies must flow through inputs and outputs. Treat every explicit dependency as technical debt requiring a comment explaining why implicit references failed. Future maintainers, including yourself during incident response at 2 AM, will thank you.

Applying Terraform depends_on Safely in Production

Treat Terraform depends_on: When and Why as a question with a default answer of "don't." Reserve explicit dependencies for verified side-effect scenarios, isolate them behind time_sleep or dedicated null resources, and always document the rationale. Prefer implicit attribute references for data flow, design modules with completion-signaling outputs, and validate ordering through graph visualization before committing workarounds. Your infrastructure will be faster to apply, safer to destroy, and easier to reason about during outages. If your team struggles with dependency management or needs an audit of existing Terraform codebases, reach out for infrastructure consulting to establish patterns that scale.

Frequently Asked Questions

It forces explicit execution order when implicit dependencies fail. Use it only when resource B requires resource A to exist first, but no attribute reference links them. This overrides parallel creation to prevent race conditions during apply operations in 2026 infrastructure deployments.

Avoid it when standard attribute references suffice. Implicit dependencies allow parallel provisioning and better state management. Explicit ordering blocks concurrency, increases apply time, and creates fragile configuration chains that break easily during refactoring or module upgrades across your infrastructure codebase.

Yes, data sources support depends_on since Terraform 0.13. Use this when reading external state requires another resource to finish provisioning first. This prevents stale reads during initial applies where the target object does not yet exist in the remote API or cloud provider.

No, depends_on cannot be nested within dynamic blocks. It must be declared at the resource or module level. Move the dependency outside the dynamic iteration to ensure proper evaluation order during the planning phase without causing syntax errors or unexpected graph behavior.

It serializes resource operations that could otherwise run concurrently. Each explicit dependency adds sequential wait time to the execution graph. In large stacks with hundreds of resources, overusing depends_on significantly extends total apply duration by preventing the scheduler from optimizing parallel API calls.

Implicit dependencies arise from attribute references like aws_instance.web.subnet_id. Terraform automatically infers order. Explicit depends_on manually declares relationships without data flow. Prefer implicit links because they survive refactoring, enable parallelism, and provide clearer intent through actual configuration values rather than arbitrary ordering constraints.

Destroy reverses dependency order. If Resource A depends on B, Terraform deletes A first. Circular dependencies or missing resources cause failures. Check terraform graph output to visualize deletion sequence. Remove unnecessary depends_on declarations or add lifecycle create_before_destroy to resolve ordering conflicts during teardown operations.

Yes, reference outputs from child modules instead of internal resources. Cross-module depends_on should target module-level outputs to maintain encapsulation. Directly referencing nested resources breaks abstraction boundaries and creates tight coupling that complicates future refactoring and version upgrades across shared infrastructure components.

Run terraform graph -type=plan to visualize the execution DAG. Look for unexpected edges connecting unrelated nodes. Use TF_LOG=DEBUG to trace dependency resolution. Compare actual versus expected ordering. Often the issue stems from redundant explicit dependencies conflicting with implicit attribute references already present in the configuration.

No, depends_on controls order not replacement. Upstream changes only trigger downstream recreation if an attribute reference exists. Pure ordering dependencies do not propagate taints. Add triggers or replace_triggered_by in lifecycle blocks if you need cascading replacements based on upstream resource modifications during applies.

Yes, CDKTF exposes addDependency methods on constructs. The synthesized HCL includes standard depends_on metadata. Behavior matches native Terraform exactly. Use construct.node.addDependency in TypeScript or Python to enforce ordering when implicit references through props are insufficient for complex multi-stack orchestration scenarios.

Terraform detects cycles during validation and refuses to plan. The error message lists all resources involved in the circular dependency. Break the cycle by removing one explicit link, restructuring into separate modules, or using null_resource with triggers to decouple tightly coupled initialization sequences safely.

Not directly. depends_on accepts only static references. Wrap conditional logic in count or for_each on the dependent resource itself. Alternatively, use a null_resource with conditional triggers as an intermediary dependency anchor to achieve variable-driven ordering without syntax errors in HCL configurations.

No, OpenTofu maintains full compatibility with Terraform depends_on semantics through 2026. Syntax, graph resolution, and error messages remain identical. Migration between tools requires no changes to dependency declarations. Both engines enforce the same execution ordering guarantees and validation rules for explicit resource relationships.

Always add comments explaining why implicit dependencies are insufficient. Future maintainers need context for non-obvious ordering requirements. Without documentation, developers may remove seemingly redundant dependencies during cleanup, reintroducing race conditions. Include the specific API behavior or timing constraint that necessitates explicit ordering in your infrastructure code.