What is the root module vs. child modules, and how do outputs flow between them?
Quick Answer
The **root module** is the configuration in the directory where you run `terraform apply`; any module it calls (via a `module "x" { source = ... }` block) becomes a **child module**. Data flows down via the arguments passed into the module block (which populate the child's input variables) and flows back up via the child's `output` blocks, referenced from the parent as `module.x.output_name`. Child modules can themselves call further modules, forming a tree, but outputs only propagate one level at a time — a grandchild's output must be re-exposed by its parent module to reach the root.
Detailed Answer
This question tests whether you understand how data actually flows through a tree of modules, not just that modules exist.
Root module
The root module is simply the configuration in the directory where you invoke terraform plan/apply — there's no special syntax marking it as "root"; it's root by virtue of being the entry point.
main.tf <- root module
modules/
vpc/
main.tf <- child module "vpc"
Calling a child module
# root main.tf
module "vpc" {
source = "./modules/vpc"
cidr_block = "10.0.0.0/16"
}
resource "aws_instance" "web" {
subnet_id = module.vpc.public_subnet_id # consuming the child's output
}
# modules/vpc/main.tf
variable "cidr_block" { type = string }
resource "aws_vpc" "main" {
cidr_block = var.cidr_block
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.cidr_block, 8, 0)
}
output "public_subnet_id" {
value = aws_subnet.public.id
}
How data flows
- Downward: the root passes
cidr_block = "10.0.0.0/16"into the module block; this populates the child module'svariable "cidr_block". - Upward: the child module's
output "public_subnet_id"becomes accessible in the root asmodule.vpc.public_subnet_id.
Multi-level nesting
If modules/vpc itself calls a further child module (a "grandchild" relative to root), the grandchild's outputs are only visible to its direct parent (modules/vpc) unless that parent explicitly re-declares an output block re-exposing the grandchild's value. Outputs don't automatically bubble up multiple levels — each module boundary requires an explicit output to pass a value further up the tree.
Why this matters
Understanding this one-level-at-a-time propagation is essential for designing module interfaces: if you nest modules several levels deep, you must deliberately thread outputs upward at every level, which is itself a good argument for keeping module hierarchies shallow.