What is the root module vs. child modules, and how do outputs flow between them?

4 minintermediateterraformmodulesroot-module

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's variable "cidr_block".
  • Upward: the child module's output "public_subnet_id" becomes accessible in the root as module.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.

Related Resources