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 serviceresource "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 invocationsmodule "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 requirementsvariable "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)}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 }}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 modulemodule "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"}
# GitHubmodule "shared" { source = "github.com/mycompany/terraform-modules//networking?ref=v2.0.0"}
# Git with SSHmodule "internal" { source = "git::ssh://git@github.com/mycompany/terraform-modules.git//ecs?ref=v1.5.0"}Calling Modules
# root module / main.tfmodule "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 outputsoutput "auth_service_sg" { value = module.auth_service.security_group_id}Passing Providers to Modules
# When a module needs a specific provider aliasprovider "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 }}