HomeRoadmapsTerraform Day 4
DevOps Track Β· Day 4 Modular Design Β· 20 min

Modular Architecture
DevOps Terraform Sprint β€” Day 4

Refactor monolithic HCL definitions into self-contained reusable modules with explicit input contracts and outputs

πŸ› οΈ Tooling: Local Terraform Modules🎭 Role: DevOps & Infrastructure Quality Engineer
TerraformModulesHCLDry ArchitectureReusability
About Today's 20-Minute Lab

As infrastructure grows across environments (Development, Staging, Production), copying and pasting main.tf files causes code duplication and drift. Today on Day 4, you will package infrastructure into a reusable local module (./modules/docker_web_app). Modules encapsulate resources behind clean, reusable API interfaces.

🎬 Day 4 Video Walkthrough
πŸ“¦Sample Codebase & Working Solution (Day 4)Available Now
View on GitHub

The verified Day 4 sample code is live in the public GitHub repository. Clone it to get the complete working solution immediately.

Git Clone β€” Available Now
git clone https://github.com/letstrnsfrm-ai/devops-terraform-roadmap.git
cd devops-terraform-roadmap/day-4-modules
1

Module Directory & Child Blueprint

GOAL
Create the child module directory structure in ./modules/docker_web_app.
DIR SETUP
Create module folder structure:
Terminal β€” Create Module Directory
mkdir -p ~/terraform-labs/day-4-modules/modules/docker_web_app
cd ~/terraform-labs/day-4-modules
CHILD MAIN.TF
Create ./modules/docker_web_app/main.tf:
HCL β€” ./modules/docker_web_app/main.tf
terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 3.0.1"
    }
  }
}

resource "docker_image" "app" {
  name         = var.image_name
  keep_locally = false
}

resource "docker_container" "app" {
  name  = var.app_name
  image = docker_image.app.image_id

  ports {
    internal = var.internal_port
    external = var.external_port
  }
}
CHILD VARIABLES.TF
Create ./modules/docker_web_app/variables.tf:
HCL β€” ./modules/docker_web_app/variables.tf
variable "app_name" { type = string }
variable "image_name" { type = string, default = "nginx:alpine" }
variable "internal_port" { type = number, default = 80 }
variable "external_port" { type = number }
🏭Production Context & Enterprise Real-World Implementation
πŸ’‘ Why We Are Doing This: Encapsulation isolates complex underlying resource structures behind simple input contracts, standardizing infrastructure quality across enterprise development teams.
🏒 Real-World Production Usecase: Platform teams (at Spotify or AirBnB) author standardized Terraform modules for microservices, database clusters, and VPCs. Developer teams simply consume approved modules without re-inventing security rules.
βš™οΈ How Implemented in Production: Production child modules are hosted in private Git repositories or Terraform Private Registries, enabling semantic versioning (e.g. `source = 'git::https://github.com/org/tf-modules.git//web_app?ref=v2.1.0'`).
2

Instantiating Modules in Root Blueprint

GOAL
Instantiate multiple instances of your child module (e.g. Frontend Web and Admin Portal) in the root main.tf.
ROOT MAIN.TF
Create root main.tf referencing the child module twice:
HCL β€” ./main.tf
terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 3.0.1"
    }
  }
}

provider "docker" {}

# Module Instance 1: Frontend Web App
module "frontend_app" {
  source        = "./modules/docker_web_app"
  app_name      = "qe_frontend_service"
  external_port = 8086
}

# Module Instance 2: Admin Portal Service
module "admin_app" {
  source        = "./modules/docker_web_app"
  app_name      = "qe_admin_portal_service"
  external_port = 8087
}
🏭Production Context & Enterprise Real-World Implementation
πŸ’‘ Why We Are Doing This: Module reusability eliminates thousands of lines of duplicated HCL, making infrastructure readable, maintainable, and easy to audit.
🏒 Real-World Production Usecase: SaaS companies instantiate the same core microservice module tens or hundreds of times across different regional data centers (US-East, EU-Central, AP-South).
βš™οΈ How Implemented in Production: In root production code, modules are instantiated with environment tags, cost-center allocation tags, and monitoring integration hooks built right into the module contract.
3

Module Initialization & Provisioning

GOAL
Run terraform init to index local module sources and provision both container instances.
PREREQ
Ensure your Docker daemon is running before applying:
Terminal β€” Docker Daemon Setup (choose your OS)
# macOS β€” Docker Desktop (GUI: open app) OR Colima (CLI):
colima start
export DOCKER_HOST="unix://$HOME/.colima/default/docker.sock"

# Windows β€” ensure Docker Desktop is running with WSL2 backend (no extra command needed)

# Linux:
sudo systemctl start docker
# then verify: docker ps
WORKFLOW
Initialize and apply the root module blueprint:
Terminal β€” Init & Apply Modules
# 1. Index local modules
terraform init

# 2. Plan and provision both instances
terraform apply -auto-approve
🏭Production Context & Enterprise Real-World Implementation
πŸ’‘ Why We Are Doing This: `terraform init` downloads and indexes child module dependencies into the `.terraform/modules` directory, creating an immutable dependency tree.
🏒 Real-World Production Usecase: Automated deployment pipelines verify that all module version locks match security guidelines before planning changes.
βš™οΈ How Implemented in Production: Production CI systems run `terraform init -get-plugins=true` inside isolated ephemeral containers to guarantee clean, un-tampered module downloading.
4

Multi-Instance Verification & Teardown

GOAL
Verify both module container endpoints and destroy the multi-instance environment.
VERIFICATION
Wait a moment for port binding, then curl both endpoints:
Terminal β€” Verify Both Containers
# Allow container port binding to settle
sleep 2

# macOS / Linux β€” Test Frontend App (Port 8086)
curl -I http://localhost:8086

# macOS / Linux β€” Test Admin App (Port 8087)
curl -I http://localhost:8087

# Windows PowerShell equivalent:
# Invoke-WebRequest -Uri http://localhost:8086
# Invoke-WebRequest -Uri http://localhost:8087

# Destroy managed modular resources
terraform destroy -auto-approve
EXPECTED OUTPUT
Verification Success
HTTP/1.1 200 OK returned on both port 8086 and 8087.
Destroy complete! Resources: 4 destroyed.
🏭Production Context & Enterprise Real-World Implementation
πŸ’‘ Why We Are Doing This: Verifying independent instances spawned from a shared module guarantees that module parameters do not collide or overwrite global network names.
🏒 Real-World Production Usecase: High-scale platforms verify that multi-region microservice deployments spun up via modules pass health checks independently.
βš™οΈ How Implemented in Production: Automated integration testing tools (like Terratest in Go) spin up ephemeral module instances, run HTTP/API assertions, and run `destroy` upon test completion.