Skip to content

Dependencies Between Units

A dependency block lets one unit read another unit’s outputs even though the two units have entirely separate Terraform state, and mock_outputs lets that dependent unit still plan before the unit it depends on has actually been applied.

Crossing the state boundary: why module composition is not enough

Section titled “Crossing the state boundary: why module composition is not enough”

The Modules module showed one Terraform module’s output feeding straight into another module’s input — a network_id produced by a vpc module, passed as an argument into a compute module, both wired together inside a single main.tf and applied together as one terraform apply against one state file. That composition works because both modules live inside the same Terraform run: Terraform can see the whole dependency graph and resolve it in one pass.

Terragrunt units break that assumption on purpose. A vpc unit and a compute unit each have their own terragrunt.hcl, their own state file, and their own independent terraform apply. Nothing about a plain Terraform module block reaches across that boundary, because as far as either unit’s own Terraform run is concerned, the other unit’s resources do not exist. Wiring a vpc unit’s network into a compute unit therefore needs a mechanism built for exactly this gap — a dependency block.

The dependency block: reading another unit outputs

Section titled “The dependency block: reading another unit outputs”

A dependency block names another unit by its directory and exposes that unit’s outputs as values this unit can use directly inside its own inputs:

compute/terragrunt.hcl
include "root" {
path = find_in_parent_folders("root.hcl")
}
dependency "vpc" {
config_path = "../vpc"
mock_outputs = {
network_id = "mock-network-id"
}
mock_outputs_allowed_terraform_commands = ["plan"]
}
terraform {
source = "git::[email protected]:acme/infrastructure-modules.git//compute?ref=v1.2.0"
}
inputs = {
network_id = dependency.vpc.outputs.network_id
}

config_path = "../vpc" points at the vpc unit’s directory. Under the hood, Terragrunt runs the equivalent of terraform output against that unit’s state and exposes the result as dependency.vpc.outputs, so dependency.vpc.outputs.network_id reaches into the vpc unit’s actual applied state and pulls out the value of its network_id output — the same output name the vpc unit’s own Terraform module would have declared with an output block. That value then flows straight into this unit’s inputs, exactly like any other input value.

mock_outputs: letting plan succeed before the dependency is applied

Section titled “mock_outputs: letting plan succeed before the dependency is applied”

Reading a real output only works once the vpc unit has actually been applied and has real state to read from. Before that, a plan on compute would fail outright, since dependency.vpc.outputs.network_id would have nothing to resolve to. mock_outputs solves that by supplying a placeholder value Terragrunt substitutes in whenever the dependency has not been applied yet, and mock_outputs_allowed_terraform_commands restricts exactly which commands are allowed to use that placeholder. In the example above, mock-network-id stands in for the real value only during plan — a genuine apply still requires the vpc unit’s real output, so a placeholder value can never accidentally get written into real infrastructure.

Apply order: computed automatically from dependency blocks

Section titled “Apply order: computed automatically from dependency blocks”

Because a dependency block names another unit explicitly, Terragrunt can see the full graph of which units depend on which, across an entire directory tree. When you run an operation across many units at once — the subject of the next lesson — Terragrunt uses that graph to compute the correct order on its own: the vpc unit gets applied before the compute unit that reads its outputs, without anyone hard-coding that ordering or remembering to run the units in the right sequence by hand.

flowchart LR
  vpc["vpc unit (own state)"] -->|terraform output| dep["dependency vpc: outputs.network_id"]
  dep --> inputsBlock["compute unit inputs: network_id"]
  mock["mock_outputs: network_id = mock-network-id"] -.->|used only for plan, before vpc is applied| dep
vpc unit outputs flowing into a compute unit dependency block and inputs
Why is a plain Terraform module composition pattern not enough to connect a vpc unit to a compute unit in Terragrunt?
What is mock_outputs used for in a dependency block?
How does Terragrunt determine the correct order to apply multiple dependent units in?
Inside the compute unit inputs block, what does dependency.vpc.outputs.network_id refer to?