liveBook · Manning

Submodule

As explained in Terraform in Depth: Infrastructure as Code with Terraform and OpenTofu

A submodule is a Terraform module distributed as part of another module and generally coupled to its parent module.

Suppose one Terraform module must build a network across several availability zones, with a choice of subnet layouts. A high-level module can expose the choice and call submodules to build each location’s network. This keeps the interface simple while moving topology-specific resources into smaller components. section 10.2.2

A submodule is a module distributed inside a larger parent module, rather than separately. The parent’s modules directory is the strong convention for storing it, though not a requirement. A submodule has the structure and documentation expected of any module; it can also be called and tested individually. Its purpose is to divide the parent’s functionality into manageable pieces. section 3.1 section 3.1.4

Build a network location first

For a two-segment location, a submodule accepts a CIDR block, VPC ID, and availability zone. It uses cidrsubnet to split the address range into public and private subnets, and creates a NAT gateway so the private subnet can reach the internet. A three-segment variant also creates an isolated subnet. Build and test these lower-level components before wrapping them in the high-level module, but do not mistake an individually callable submodule for a complete deployment: the high-level module is responsible for the internet gateway. section 10.2.3

Listing 10.6 Public and private subnet module
variable "cidr_block" {
  description = "The CIDR block for the network."
  type        = string
}
 
variable "vpc_id" {
  description = "The ID of the VPC."
  type        = string
}
 
variable "availability_zone" {
  description = "The availability zone."
  type        = string
}
 
locals {
  private_subnet_cidr_block = cidrsubnet(var.cidr_block, 1, 0) #1
  public_subnet_cidr_block  = cidrsubnet(var.cidr_block, 1, 1) #1
}
 
 
#
# Public Network
#
 
resource "aws_subnet" "public" {
  vpc_id                  = var.vpc_id
  cidr_block              = local.public_subnet_cidr_block     #2
  map_public_ip_on_launch = true                               #3
  availability_zone       = var.availability_zone
  tags = {
    Network = "Public"                                         #4
  }
}
 
resource "aws_eip" "nat" {
  tags = {}
}
 
resource "aws_nat_gateway" "main" {                            #5
  allocation_id = aws_eip.nat.id                               #6
  subnet_id     = aws_subnet.public.id                         #7
  tags          = {}
}
 
 
#
# Private Network
#
 
resource "aws_subnet" "private" {                              #8
  vpc_id            = var.vpc_id
  cidr_block        = local.private_subnet_cidr_block          #9
  availability_zone = var.availability_zone                    #10
  tags = {
    Network = "Private"
  }
}
 
resource "aws_route_table" "private" {                         #11
  vpc_id = var.vpc_id
}
 
resource "aws_route_table_association" "private" {             #12
  subnet_id      = aws_subnet.private.id
  route_table_id = aws_route_table.private.id
}
 
resource "aws_route" "internet_access" {                       #13
  route_table_id         = aws_route_table.private.id
  nat_gateway_id         = aws_nat_gateway.main.id
  destination_cidr_block = "0.0.0.0/0"                         #14
}
 
output "private_subnet_id" {                                   #15
  value = aws_subnet.private.id
}
 
output "public_subnet_id" {                                    #16
  value = aws_subnet.public.id
}

The public-and-private subnet configuration shows the submodule’s inputs, cidrsubnet calculations, and AWS resources together. section 10.2.3

Keep the parent-child boundary deliberate. Publishing the location components as standalone modules offers more direct control, while keeping them behind a high-level module favors ease of use. Avoid adding submodules inside submodules merely to split work further: deeper nesting raises project complexity. Module depth also appears in Terraform resource addresses used in command output and state, so a clear hierarchy helps when navigating resources. section 10.2.2 section 3.1.4 section 10.1.2

How Terraform in Depth: Infrastructure as Code with Terraform and OpenTofu covers it

How deep it goes
Defines placement and callability, then works through subnet-building inputs and resources; warns against deeper nesting.
Examples
Uses two- and three-segment AWS subnet modules and a multi-location network assembled by a high-level module.
What it assumes
Introduces root and shared modules before submodules, then explains the network components used in the worked configuration.
What you can do after
Build it from scratch: implement location submodules, wrap them in a high-level network module, and assign parent-owned infrastructure appropriately.
How it connects
Links submodules to root modules, shared modules, network topologies, and hierarchical Terraform resource addresses.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Terraform in Depth: Infrastructure as Code with Terraform and OpenTofu. The section links point to the parts of the book it draws on.