VOOZH about

URL: https://dev.to/the-software-s-journey/to-the-moon-terraform-ep6-54l3

⇱ To The Moon Terraform Ep.6 - DEV Community


"The Saturn V was not one thing. It was three stages, a command module, a service module, and a lunar module — each designed independently, each tested independently, each capable of being replaced independently. The genius was not in any single component. The genius was in the interface between them."


🌕 Episode 6 — The Modular Rocket

Let us consider, briefly, the engineering absurdity of building everything from scratch every time.

Imagine if NASA, having built the first Saturn V rocket, responded to the second Moon mission by saying: "Right. Let's design a new rocket from first principles." The fuel tank: reinvented. The guidance system: rewritten. The escape tower: reconsidered.

No organisation on Earth would do this. And yet, without Terraform Modules, this is precisely what infrastructure engineers do.

They write the same VPC configuration in every project. They copy-paste security group rules. They duplicate S3 bucket configurations with subtle differences that introduce subtle bugs. They re-invent, again and again, components that were already correct the first time.

Modules are how you build a rocket once and fly it many times.


🧱 What Is a Module?

A Terraform Module is simply a directory of .tf files. Any directory of Terraform configuration is a module — including the directory you have been working in, which is called the root module.

What makes modules powerful is calling one module from another — passing inputs in, receiving outputs back, hiding the internal complexity.

mission-apollo/ ← Root module (your project)
├── main.tf ← Calls child modules
├── variables.tf
├── outputs.tf
│
└── modules/ ← Child modules (reusable components)
 ├── network/ ← VPC, subnets, gateways
 │ ├── main.tf
 │ ├── variables.tf
 │ └── outputs.tf
 ├── compute/ ← EC2 instances, launch templates
 │ ├── main.tf
 │ ├── variables.tf
 │ └── outputs.tf
 └── security/ ← Security groups, IAM roles
 ├── main.tf
 ├── variables.tf
 └── outputs.tf

Each module is a stage of the rocket: designed independently, tested independently, assembled into the complete vehicle.


🔧 Writing a Module: The Network Stage

# modules/network/variables.tf

variable "mission_name" {
 description = "Mission name prefix for all resources"
 type = string
}

variable "vpc_cidr" {
 description = "CIDR block for the VPC"
 type = string
 default = "10.0.0.0/16"
}

variable "public_subnet_count" {
 description = "Number of public subnets"
 type = number
 default = 2
}

variable "environment" {
 description = "Deployment environment"
 type = string
}
# modules/network/main.tf

locals {
 name_prefix = "${var.mission_name}-${var.environment}"
}

resource "aws_vpc" "this" {
 cidr_block = var.vpc_cidr
 enable_dns_hostnames = true
 enable_dns_support = true
 tags = { Name = "${local.name_prefix}-vpc" }
}

resource "aws_internet_gateway" "this" {
 vpc_id = aws_vpc.this.id
 tags = { Name = "${local.name_prefix}-igw" }
}

resource "aws_subnet" "public" {
 count = var.public_subnet_count
 vpc_id = aws_vpc.this.id
 cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
 availability_zone = data.aws_availability_zones.available.names[count.index]
 map_public_ip_on_launch = true
 tags = { Name = "${local.name_prefix}-public-${count.index + 1}" }
}

data "aws_availability_zones" "available" {
 state = "available"
}
# modules/network/outputs.tf

output "vpc_id" {
 description = "The ID of the VPC"
 value = aws_vpc.this.id
}

output "public_subnet_ids" {
 description = "List of public subnet IDs"
 value = aws_subnet.public[*].id
}

output "internet_gateway_id" {
 description = "The ID of the Internet Gateway"
 value = aws_internet_gateway.this.id
}

🚀 Calling a Module: The Assembly

# main.tf — Root module: assembly of the rocket stages

# Stage 1: The Network (VPC and subnets)
module "network" {
 source = "./modules/network" # Local path to the module

 mission_name = var.mission_name
 environment = var.environment
 vpc_cidr = "10.0.0.0/16"
 public_subnet_count = 3
}

# Stage 2: The Compute (using network outputs as inputs)
module "compute" {
 source = "./modules/compute"

 mission_name = var.mission_name
 environment = var.environment
 vpc_id = module.network.vpc_id # Output from network → input to compute
 subnet_ids = module.network.public_subnet_ids # Output from network → input to compute
 instance_type = var.instance_type
}

# Stage 3: The Security (IAM roles and policies)
module "security" {
 source = "./modules/security"

 mission_name = var.mission_name
 environment = var.environment
}

The elegance: module.network.vpc_id — the output of one stage feeds directly into the input of the next. The interfaces between stages are explicit, typed, and documented.


🌐 Public Modules: The Supplier Network

You do not need to write every module yourself. The Terraform Registry (registry.terraform.io) hosts thousands of community and verified modules — pre-built rocket stages from trusted suppliers.

# Using official AWS VPC module from the Terraform Registry
module "vpc" {
 source = "terraform-aws-modules/vpc/aws"
 version = "~> 5.0" # Pin the version — always

 name = "${var.mission_name}-vpc"
 cidr = "10.0.0.0/16"

 azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
 private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
 public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

 enable_nat_gateway = true
 enable_vpn_gateway = false

 tags = {
 Mission = var.mission_name
 Environment = var.environment
 ManagedBy = "terraform"
 }
}

This single module call replaces hundreds of lines of VPC configuration — and it has been tested by thousands of teams worldwide.


📊 The SIPOC of Episode 6

🔵 Supplier 🟡 Input 🟢 Process 🟠 Output 🔴 Consumer
Root module module {} block with source + input variables Terraform resolves module source path or downloads from Registry Module resource definitions merged into plan Terraform planning and apply engine
Module's variables.tf Values passed in module {} block Input validation and type checking Populated var.* namespace within module Module's internal main.tf resources
Module's main.tf Provider configuration (inherited from root) Resources planned and applied Real cloud resources Module's outputs.tf value resolution
Module's outputs.tf Attributes of resources created within module Value extraction and exposure Named outputs (module.<name>.<output>) Root module and other modules referencing them

🌕 Next episode: **Mission Control Systems* — Remote State and Backends. Because a flight recorder that exists on only one engineer's laptop is not a flight recorder.*