skip to content

In Terraform, what are the consequences of putting `depends_on` on a `module` block instead of wiring modules together through their outputs?

level: seniorimportance: should knowfreq 36%

answer

  1. a value reference is already a dependency
  2. coarsest possible edge: everything to everything
  3. parallelism and destroy ordering both suffer
  4. data sources deferred to apply time
  5. last resort for behavioural dependencies

basics

~20 s

It makes every object in the module wait for everything in the target, which serialises work that could run in parallel and hides the relationship from the code. It also defers data sources inside the module to apply time, so the plan fills with "(known after apply)".

solid answer

~50 s

`depends_on` on a module is the bluntest edge you can draw. Instead of one resource depending on one value, every object inside the module depends on everything in the target, so work that Terraform would have run in parallel becomes sequential and the whole module is held back by the slowest thing it now waits on. Worse for day-to-day use, any data source inside the module can no longer be read during plan — Terraform defers it to apply because its dependencies may still change — so its attributes and everything derived from them show as `(known after apply)` and the plan stops telling you what will happen. It is documented as a last resort, for behavioural dependencies you genuinely cannot express as a value, like IAM permissions that must exist before an instance uses them at runtime. The default should be passing `module.network.subnet_ids` into an input, which creates a precise edge and keeps the plan concrete.

code

hcl · 9 lines
hcl
module "network" {
  source = "./modules/network"
  cidr   = "10.0.0.0/16"
}

module "app" {
  source     = "./modules/app"
  subnet_ids = module.network.private_subnet_ids
}

go deeper

for a junior

Know that referencing another module's output already creates the ordering, and that depends_on is an explicit extra edge you rarely need to write yourself.

for a middle

Be ready to explain the mechanics: a module-level depends_on makes every object inside depend on every object in the target, which removes parallelism and defers data-source reads to apply time.

for a senior

Demonstrate the diagnosis — when a plan comes back mostly (known after apply), trace it to a deferred data source behind an explicit edge, then refactor by passing values in so the plan is concrete before anyone approves it.

for a principal

Own the review standard: treat module-level depends_on as a defect to justify, since it encodes an undocumented coupling nobody will dare remove, and set the expectation that modules take their dependencies as inputs rather than discovering them.

## Two ways to order work Terraform decides ordering from a dependency graph it builds by reading references. When the root writes `subnet_ids = module.network.private_subnet_ids`, that reference *is* the edge: the database resources that consume those ids wait for the subnets, and nothing else does. This is an **implicit** dependency, and it is precise because it connects the specific objects that exchange the value. `depends_on` is the **explicit** escape hatch. Available on `module` blocks since Terraform 0.13, it accepts static references to resources, data sources and other modules — not arbitrary expressions: ```hcl module "app" { source = "./modules/app" depends_on = [module.network] } ``` ## What that edge actually means It is not "start this module a bit later". Terraform treats every object declared inside `module.app` as depending on every object inside `module.network`. Three consequences follow. **Parallelism collapses.** Resources in the module that had nothing to do with the network — an SNS topic, a log group — now wait for the last network resource to finish. On a large module that is real wall-clock time added to every apply, and on destroy the ordering reverses and drags the same way. **The reason disappears from the code.** An input variable says *what* is shared. `depends_on = [module.network]` says only *that* something is. Six months later nobody can tell whether it is still needed, and because removing it is untestable except by applying, it never gets removed. **Plans become vague.** This is the consequence that bites daily. A data source whose dependencies might change cannot be safely read during plan, so Terraform defers reading it until apply. Any data source inside a module carrying `depends_on` is a candidate, and everything computed from it becomes `(known after apply)`. You approve a plan that no longer shows the values you were reviewing — which is exactly the thing a plan exists to prevent. ## When it is the right tool The legitimate case is a **behavioural** dependency: module B does not consume any value from A, but will fail or misbehave at runtime unless A exists. The canonical example is IAM — an instance profile's policy must be attached before the instance boots and calls the API, yet nothing in the instance's arguments references the policy. There is no value to pass, so no implicit edge exists, and `depends_on` is the honest expression of it. Even then, prefer the smallest scope that works. Putting `depends_on` on the single resource inside the module that has the behavioural need costs you far less than putting it on the module block, because only that resource is serialised and only its dependents lose plan-time detail. ## The refactor that usually removes it Most module-level `depends_on` in real repositories is a workaround for a module that builds or looks up its own dependency. If the app module calls a data source to find "the" VPC by tag, it has no reference to the network module and therefore needs a manual edge. Invert it: have the module take `vpc_id` and `subnet_ids` as inputs and let the root pass `module.network.vpc_id`. The manual edge disappears, the data source disappears, the plan becomes concrete again, and the module becomes usable against a network it did not create. ```hcl # preferred: the reference is the dependency module "app" { source = "./modules/app" subnet_ids = module.network.private_subnet_ids } ``` ## One hard incompatibility A module that declares its own provider configuration cannot use `depends_on` at all — the same restriction that blocks `count` and `for_each` on such modules. If you meet that error, the module needs its `provider` block removed and its provider configurations passed in by the caller. ## What an interviewer is listening for Weak answers describe `depends_on` as "how you control order in Terraform", which suggests the candidate has never relied on implicit edges. Strong answers state the default (pass the value), name the specific costs of the module-scoped edge — lost parallelism, an undocumented relationship, and `(known after apply)` swallowing the plan — and can give the one class of case, behavioural dependencies with no value to exchange, where it is correct.

  • You still need a behavioural dependency. How do you limit the damage?
    Put `depends_on` on the single resource that has the runtime need rather than on the whole module block. Only that resource and its dependents are serialised, the rest of the module still plans and applies in parallel, and far less of the plan turns into `(known after apply)`. Leave a comment naming the runtime reason, because nothing else in the code records it.
  • Why does a plan fill with (known after apply) when a module carries depends_on?
    Terraform cannot safely read a data source whose dependencies may still change, so it defers the read to apply time. Every attribute of that data source is unknown during plan, and anything computed from it — names, policy documents, resource arguments — becomes unknown too. You end up approving a change whose concrete values you never saw.
  • Can you write depends_on = [module.network.vpc_id]?
    No. `depends_on` takes static references to whole objects — resources, data sources, modules — not attribute expressions. If you have an attribute to point at, that is the signal you should be passing it as an input variable instead, which creates the dependency edge implicitly and precisely.

saying these in an interview costs you the question

  • Calls depends_on the normal way to order Terraform resources
  • Thinks it only delays the module's start slightly
  • Unaware data sources get deferred to apply
  • Adds it defensively to every module block
  • Believes it can reference a specific attribute

context