Cloud  /  Terraform

IaC Terraform 50 guides · updated 2026

Infrastructure as code done right — providers, state, reusable modules, and the workflow patterns that keep multi-cloud deployments sane in 2026.

Terraform Modules

A Terraform module is any directory containing .tf files. Every Terraform configuration is itself a module — the “root module.” Child modules are reusable building blocks called by the root module (or by other modules). Modules turn duplicated infrastructure patterns into single, tested, composable units.


Why Use Modules

Without modules, you copy-paste similar infrastructure configurations for each service:

# Without modules — duplicated for every service
resource "aws_security_group" "auth_service_sg" { ... }
resource "aws_ecs_task_definition" "auth_service" { ... }
resource "aws_ecs_service" "auth_service" { ... }
resource "aws_lb_target_group" "auth_service" { ... }
resource "aws_security_group" "payment_service_sg" { ... }
resource "aws_ecs_task_definition" "payment_service" { ... }
resource "aws_ecs_service" "payment_service" { ... }
resource "aws_lb_target_group" "payment_service" { ... }

With a module:

# With modules — one definition, two invocations
module "auth_service" {
source = "./modules/ecs-service"
name = "auth-service"
image = "mycompany/auth:v1.2.3"
cpu = 256
memory = 512
}
module "payment_service" {
source = "./modules/ecs-service"
name = "payment-service"
image = "mycompany/payment:v2.0.0"
cpu = 512
memory = 1024
}

Module Structure

A well-organized module:

modules/ecs-service/
├── main.tf # Resources
├── variables.tf # Input variables
├── outputs.tf # Output values
└── versions.tf # Provider and Terraform version requirements
modules/ecs-service/variables.tf
variable "name" {
description = "Service name — used in resource naming"
type = string
}
variable "image" {
description = "Container image URI (e.g., 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:v1.0.0)"
type = string
}
variable "cpu" {
description = "CPU units (256 = 0.25 vCPU)"
type = number
default = 256
}
variable "memory" {
description = "Memory in MiB"
type = number
default = 512
}
variable "desired_count" {
type = number
default = 1
}
variable "cluster_id" {
description = "ECS cluster ARN"
type = string
}
variable "vpc_id" {
type = string
}
variable "subnet_ids" {
type = list(string)
}
modules/ecs-service/main.tf
resource "aws_ecs_task_definition" "this" {
family = var.name
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = var.cpu
memory = var.memory
container_definitions = jsonencode([{
name = var.name
image = var.image
portMappings = [{ containerPort = 8080, protocol = "tcp" }]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.this.name
"awslogs-region" = data.aws_region.current.name
}
}
}])
}
resource "aws_cloudwatch_log_group" "this" {
name = "/ecs/${var.name}"
retention_in_days = 14
}
resource "aws_security_group" "this" {
name = "${var.name}-sg"
vpc_id = var.vpc_id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_ecs_service" "this" {
name = var.name
cluster = var.cluster_id
task_definition = aws_ecs_task_definition.this.arn
desired_count = var.desired_count
launch_type = "FARGATE"
network_configuration {
subnets = var.subnet_ids
security_groups = [aws_security_group.this.id]
assign_public_ip = false
}
}
modules/ecs-service/outputs.tf
output "service_name" {
value = aws_ecs_service.this.name
}
output "security_group_id" {
value = aws_security_group.this.id
}
output "task_definition_arn" {
value = aws_ecs_task_definition.this.arn
}

Module Sources

# Local module (relative path)
module "networking" {
source = "./modules/networking"
}
# Terraform Registry — public module
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
}
# Private Registry (HCP Terraform)
module "ecs" {
source = "app.terraform.io/mycompany/ecs-service/aws"
version = "~> 2.0"
}
# GitHub
module "shared" {
source = "github.com/mycompany/terraform-modules//networking?ref=v2.0.0"
}
# Git with SSH
module "internal" {
source = "git::ssh://git@github.com/mycompany/terraform-modules.git//ecs?ref=v1.5.0"
}

Calling Modules

# root module / main.tf
module "networking" {
source = "./modules/networking"
environment = var.environment
cidr_block = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
module "auth_service" {
source = "./modules/ecs-service"
name = "auth-service"
image = "mycompany/auth:${var.image_tag}"
cluster_id = module.networking.ecs_cluster_id
vpc_id = module.networking.vpc_id
subnet_ids = module.networking.private_subnet_ids
}
# Access module outputs
output "auth_service_sg" {
value = module.auth_service.security_group_id
}

Passing Providers to Modules

# When a module needs a specific provider alias
provider "aws" {
alias = "us_east"
region = "us-east-1"
}
provider "aws" {
alias = "eu_west"
region = "eu-west-1"
}
module "us_deployment" {
source = "./modules/application"
providers = {
aws = aws.us_east
}
}
module "eu_deployment" {
source = "./modules/application"
providers = {
aws = aws.eu_west
}
}