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.
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.
| Aspect | Implicit Dependency | Explicit Dependency (depends_on) | Typical Example |
|---|---|---|---|
| How it's created | Inferred automatically from attribute references | Specified manually via the meta-argument | subnet referencing vpc.id / extra audit setup after bucket creation |
| Readability | Straightforward and easy to read | Intent is clear, but overuse hurts readability | Implicit by default; explicit only to fill the gaps |
| Scope | Limited to relationships that have references | Can be added freely even without references | Audit S3 bucket then any downstream resources, etc. |
| Destroy Order | Automatically the reverse of the reference order | Dependency edges are reversed and applied | If 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]
}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.
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]
}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.
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 itUse 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.
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
}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.
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.eksFirst 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.
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]
}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?
正解: 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.
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.
Practice with certification-focused question sets
無料で問題を解いてみる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.
HCL Syntax: Terraform's Configuration Language (2026)
HCL2 fundamentals for Terraform — blocks, attributes, expres...
Terraform Authoring & Operations Pro: Complete Guide (2026)
Tactics for the Terraform Pro exam — module authoring, works...
Terraform Providers: Plugin Management Fundamentals (2026)
Provider mechanics — required_providers, versions, mirrors, ...
Terraform Resource Blocks: Declarative Infra Units (2026)
Resource block fundamentals — addresses, references, common ...
Terraform Data Sources: Read-Only External Data (2026)
Data source basics — declaration, refresh behavior, dependen...