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