Terraform Nested Modules and Composition

Large buildings come from standard parts: walls, doors, windows, and pipes. Builders do not invent each part again for every house. Terraform modules follow the same idea. Small modules do one job well, and a larger configuration joins them together like building blocks. This style is called composition. It keeps big projects tidy and lets teams reuse proven parts.

What Nested Modules Mean

A nested module is a module called from inside another module. The top folder you run Terraform from is the root module. A child module is any module the root calls. A grandchild module sits below a child.

Diagram: Module Tree

Root Module (you run terraform here)
 |
 +-- module "network"
 |     +-- module "subnets"        (grandchild)
 |
 +-- module "compute"
 |
 +-- module "database"

Two Ways to Combine Modules

Nesting: A Module Calls a Module

The network module hides its subnet details by calling a subnet module internally. The root module never sees the inner workings.

Composition: The Root Calls Several Modules Side by Side

The root module calls network, compute, and database modules at the same level. The root passes outputs from one module into the inputs of another. Composition keeps each module independent and easy to test.

Composition in Action

A network module creates a virtual network and exposes its ID and subnet IDs as outputs. A compute module needs those values to place servers. The root module connects the two.

Diagram: Data Flowing Between Modules

+----------------+   vpc_id, subnet_ids    +----------------+
| module network | ----------------------> | module compute |
+----------------+                         +--------+-------+
        |                                           |
        | vpc_id                         instance_ids
        v                                           v
+----------------+                         +----------------+
| module database|                         |   root output  |
+----------------+                         +----------------+

Root Module Code

module "network" {
  source     = "./modules/network"
  cidr_block = "10.0.0.0/16"
}

module "compute" {
  source     = "./modules/compute"
  vpc_id     = module.network.vpc_id
  subnet_ids = module.network.public_subnet_ids
}

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

Each reference follows the pattern module.NAME.OUTPUT. Terraform reads those references and builds the network first, then the compute and database modules.

Network Module Outputs

output "vpc_id" {
  value = aws_vpc.main.id
}

output "public_subnet_ids" {
  value = aws_subnet.public[*].id
}

The Folder Layout

project/
  main.tf
  variables.tf
  outputs.tf
  modules/
    network/
      main.tf
      variables.tf
      outputs.tf
    compute/
      main.tf
      variables.tf
      outputs.tf
    database/
      main.tf
      variables.tf
      outputs.tf

Rules for Healthy Module Design

Keep Modules Small and Focused

A module should answer one question: how do I build a network, or how do I build a database. A module that creates everything becomes hard to change and hard to reuse.

Limit Nesting Depth

Two levels deep is comfortable. Four levels deep turns debugging into a treasure hunt. When a module tree grows deep, flatten it and compose at the root.

Expose Only What Others Need

Child modules do not share internal resources. A parent reads only declared outputs. Declare outputs for values other modules must use and keep the rest private.

Pass Values Down Through Variables

A child module cannot read variables from the root. The root must pass each value explicitly. Explicit inputs make every dependency visible.

Passing Providers to Modules

Child modules inherit the default provider from the root. A module that must use a different region needs an explicit provider map.

module "backup" {
  source = "./modules/backup"

  providers = {
    aws = aws.west
  }
}

Nested Module Versus Composition

ApproachStrengthRisk
NestingHides complex details behind one callDeep trees are hard to trace
CompositionEvery connection is visible in the rootThe root file grows longer

Key Points

  • The root module is the folder where you run Terraform.
  • A nested module is a module called from inside another module.
  • Composition places modules side by side and links them through outputs and inputs.
  • References use the pattern module.NAME.OUTPUT.
  • Children read only the values the parent passes in.
  • Keep modules small and keep the tree shallow.