Terraform

Terraform depends_on: Using Explicit Dependencies the Right Way

2026-04-19
NicheeLab Editorial Team

Terraform builds the dependency graph automatically from references in expressions, but when you need ordering without any reference, or you're dealing with provider consistency lag, it's safer to pin order explicitly with depends_on.

This article is aimed at Associate-level candidates and working engineers. It covers depends_on fundamentals, syntax, use cases, anti-patterns, and how to validate your graph. Version-sensitive areas come with explicit caveats.

Fundamentals: Implicit vs. Explicit Dependencies

Terraform detects attribute references between resources (for example, subnet.vpc_id = aws_vpc.main.id) and wires up implicit dependencies automatically. This is the top design principle.

But ordering without references — for instance, you want the audit log bucket to exist first, or you need to wait for IAM propagation — can't be expressed through implicit dependencies, so you pin order explicitly with depends_on. On create, depends_on runs target then source; on destroy, the order is reversed.

  • Default to creating implicit dependencies through attribute references
  • Use depends_on to make reference-less relationships and external consistency waits explicit
  • Overusing depends_on stiffens the graph and stretches plan time
  • Circular dependencies (A then B then A) cause plan to fail
AspectImplicit DependencyExplicit Dependency (depends_on)Typical Example
How it's createdInferred automatically from attribute referencesSpecified manually via the meta-argumentsubnet referencing vpc.id / extra audit setup after bucket creation
ReadabilityStraightforward and easy to readIntent is clear, but overuse hurts readabilityImplicit by default; explicit only to fill the gaps
ScopeLimited to relationships that have referencesCan be added freely even without referencesAudit S3 bucket then any downstream resources, etc.
Destroy OrderAutomatically the reverse of the reference orderDependency edges are reversed and appliedIf A depends_on B, destroy order is A then B

Implicit vs. explicit dependencies (conceptual diagram)

Implicit dependency (attribute reference)
[aws_vpc.main] ---> [aws_subnet.app]
     ^                    |
     | (references vpc_id) | (references subnet_id)
     |                    v
[aws_internet_gateway.gw]-->[aws_route_table.rt]

Explicit dependency (depends_on)
[aws_s3_bucket.audit] --(depends_on)--> [null_resource.bootstrap]
                          (pins order even without a reference)

Basic syntax (resource, module, data)

resource "aws_s3_bucket" "audit" {
  bucket = "org-audit-logs"
}

resource "aws_s3_bucket" "app" {
  bucket     = "app-bucket"
  depends_on = [aws_s3_bucket.audit]  # pins order without a reference
}

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

module "eks" {
  source     = "./modules/eks"
  depends_on = [module.network]        # pin order between modules
}

data "aws_iam_policy" "example" {
  arn        = "arn:aws:iam::aws:policy/ReadOnlyAccess"
  # Some Terraform versions allow depends_on on data sources. Use only when needed, and carefully.
  depends_on = [aws_s3_bucket.audit]
}

Syntax and Scope: Where You Can Write It, What You Can Reference

depends_on is stable on resource blocks and on module blocks (Terraform 0.13 and later). Behavior on data sources varies by version, so use it there only when you really need to, and prefer expressing the dependency via attribute references as the default.

What you list in depends_on are references to other resources or modules. Avoid swapping the target dynamically with a variable expression — keep the ordering obvious to any reader.

  • resource and module are the standard targets
  • Apply to data only as a last resort, and mind the version
  • depends_on specifies ordering only — it does not pass attribute values
  • Remember that dependencies invert on destroy

Good and bad examples (readable ordering)

# Bad: swapping the target dynamically (hurts readability)
# resource "aws_s3_bucket" "app" {
#   bucket     = "app-bucket"
#   depends_on = var.use_audit ? [aws_s3_bucket.audit] : []
# }

# Good: if the audit bucket is required, always pin it explicitly
resource "aws_s3_bucket" "app" {
  bucket     = "app-bucket"
  depends_on = [aws_s3_bucket.audit]
}

# Between modules
module "db" {
  source = "./modules/db"
}
module "api" {
  source     = "./modules/api"
  depends_on = [module.db]
}

How count and for_each Interact with depends_on

When you use count or for_each, a depends_on written once on the block applies to every instance. Per-instance depends_on isn't really an option, so the safe move is to align the ordering requirements at design time, or break the dependency apart through attribute references.

If you need different ordering per for_each key, rethink the upstream data structure or the creation order itself. Forcing depends_on to paper over the problem invites cycles or excessive serialization, and plan/apply times balloon.

  • One depends_on on the block applies to every instance at once
  • If per-instance control is required, revisit the design (push the dependency down into an attribute reference)
  • Cycles are detected at plan time, so you catch them early

How for_each behaves with depends_on

resource "aws_s3_bucket" "logs" {
  bucket = "shared-logs"
}

# We want every app bucket created after shared-logs
resource "aws_s3_bucket" "app" {
  for_each   = toset(["a", "b", "c"])
  bucket     = "app-${each.key}"
  depends_on = [aws_s3_bucket.logs]  # applied to all three
}

# Avoid per-instance ordering tricks; express the relationship via attribute references if you need it

Real-World Use Cases and Anti-Patterns

Use cases: situations where there is no reference but you still want a fixed order. For example, always create the audit log S3 bucket first and every service bucket after it, or build the network foundation (VPC, subnets, routes) before standing up EKS or ECS.

Anti-pattern: papering over transient provider inconsistencies (such as IAM propagation lag) with depends_on everywhere. Solve it first with the official retry and timeout knobs and with proper attribute references; reach for depends_on only as a targeted last resort.

  • Pin the broad foundation-then-app layer ordering
  • Establish audit and security pieces up front
  • Handle consistency lag with provider retry/timeout settings first
  • Using null_resource to control order is a last resort (avoid it whenever possible)

A careful example for IAM propagation waits

resource "aws_iam_role" "app" {
  name = "app-role"
  assume_role_policy = data.aws_iam_policy_document.assume.json
}

resource "aws_iam_role_policy_attachment" "readonly" {
  role       = aws_iam_role.app.name
  policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}

# Downstream EKS depends on IAM — start with attribute references and the official retry behavior
module "eks" {
  source     = "./modules/eks"
  depends_on = [aws_iam_role_policy_attachment.readonly]  # add only when truly necessary
}

Validation: Inspect Dependencies with graph/plan and Build a Graph That Doesn't Break

terraform graph emits DOT output that lets you take in the implicit and explicit dependencies at a glance. Spot over-serialization or the early signs of a cycle and fix the structure.

Surgical -target is handy but easily breaks the dependency graph's integrity, which makes it unfit for steady-state operations. The exam often tests that overusing it is discouraged.

  • Always check ordering and diffs with terraform plan
  • Visualize with terraform graph | dot -Tsvg
  • Reserve -target for minimal, emergency use
  • Break circular dependencies by decomposing the design

Examples of using graph/plan

# Visualize the dependency graph
$ terraform graph > graph.dot
$ dot -Tpng graph.dot -o graph.png

# Validate a single module (not recommended for steady-state ops)
$ terraform plan -target=module.eks

What the Associate Exam Targets

First lock in the principle: if an implicit dependency can be created, depends_on isn't needed. Building on that, the typical question is one where there is no reference but you still want to pin the order with depends_on.

Recurring topics include the reversed order on destroy, depends_on support on module blocks, the blanket application across count/for_each, and the risks of overusing -target.

  • Attribute references first, depends_on second (and only as needed)
  • depends_on works on modules too (since 0.13)
  • Dependencies invert on destroy
  • Even with count/for_each, a single block-level declaration applies to everything

What the wrong and right answers look like

# Wrong: piling on depends_on when a reference already exists
resource "aws_subnet" "app" {
  vpc_id     = aws_vpc.main.id  # this is enough to wire up the dependency
  cidr_block = "10.0.1.0/24"
  # depends_on = [aws_vpc.main]  # unnecessary
}

# Right: pin order only when there's no reference
resource "aws_s3_bucket" "audit" { bucket = "audit" }
resource "aws_s3_bucket" "app" {
  bucket     = "app"
  depends_on = [aws_s3_bucket.audit]
}

Check Your Understanding

Associate

問題 1

Your team wants to create an audit-log S3 bucket first and the application bucket afterward. The application bucket does not reference the audit bucket. Which is the recommended Terraform approach?

  1. Specify the audit bucket in the application bucket's resource block via depends_on
  2. Put a local-exec on a null_resource that sleeps before the bucket is created
  3. Copy the audit bucket's ARN string into a variable and reference it from the application bucket too
  4. Split terraform apply into two runs and control the order manually

正解: A

When there's no reference but you still need to pin the order, the right answer is to wire up the dependency explicitly with depends_on. Sleep tricks and manual apply runs are discouraged, and so is introducing unnecessary pseudo-references.

Frequently Asked Questions

Can I use depends_on inside a data block?

Behavior depends on the Terraform version, so prefer expressing the dependency via attribute references first. Only reach for depends_on on data blocks when you absolutely need to pin the read order, and only on a version that supports it.

Does depends_on affect destroy (delete) order too?

Yes. If A depends_on B, the create order is B then A and the destroy order is A then B. Knowing the destroy order matters in real operations.

Can I set a different depends_on for each instance of count or for_each?

Not really. A depends_on written on a block applies to every instance. If you need per-instance ordering, rethink the design and express the dependency through attribute references instead.

Check what you learned with practice questions

Practice with certification-focused question sets

無料で問題を解いてみる
Author

NicheeLab Editorial Team

NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.


Related articles
Terraform

HCL Syntax: Terraform's Configuration Language (2026)

HCL2 fundamentals for Terraform — blocks, attributes, expres...

Terraform

Terraform Authoring & Operations Pro: Complete Guide (2026)

Tactics for the Terraform Pro exam — module authoring, works...

Terraform

Terraform Providers: Plugin Management Fundamentals (2026)

Provider mechanics — required_providers, versions, mirrors, ...

Terraform

Terraform Resource Blocks: Declarative Infra Units (2026)

Resource block fundamentals — addresses, references, common ...

Terraform

Terraform Data Sources: Read-Only External Data (2026)

Data source basics — declaration, refresh behavior, dependen...

Browse all Terraform articles (102)
© 2026 NicheeLab All rights reserved.