Skip to content
5 min read·Lesson 7 of 10

Terraform Modules

Learn how Terraform modules enable reusable, composable infrastructure code — how to write them, call them, and use public registry modules.

As Terraform configurations grow, repeating the same infrastructure patterns across environments and projects becomes tedious and error-prone. Modules solve this — they are the primary unit of reuse in Terraform.

What is a Module?

Every directory containing .tf files is a module. Your main working directory is the root module. You can call other modules (child modules) from it using module blocks.

project/
  main.tf          # root module
  variables.tf
  outputs.tf
  modules/
    vpc/           # child module
      main.tf
      variables.tf
      outputs.tf
    ec2/
      main.tf
      variables.tf
      outputs.tf

Writing a Module

# modules/vpc/variables.tf
variable "vpc_cidr" {
  type    = string
  default = "10.0.0.0/16"
}

variable "environment" {
  type = string
}

variable "public_subnet_cidrs" {
  type = list(string)
}

# modules/vpc/main.tf
resource "aws_vpc" "this" {
  cidr_block = var.vpc_cidr
  tags = { Name = "${var.environment}-vpc" }
}

resource "aws_subnet" "public" {
  count             = length(var.public_subnet_cidrs)
  vpc_id            = aws_vpc.this.id
  cidr_block        = var.public_subnet_cidrs[count.index]
}

# modules/vpc/outputs.tf
output "vpc_id" {
  value = aws_vpc.this.id
}

output "public_subnet_ids" {
  value = aws_subnet.public[*].id
}

Calling a Module

# main.tf (root module)
module "vpc" {
  source = "./modules/vpc"    # local path

  vpc_cidr             = "10.0.0.0/16"
  environment          = var.environment
  public_subnet_cidrs  = ["10.0.1.0/24", "10.0.2.0/24"]
}

# Reference module outputs
resource "aws_lb" "main" {
  subnets = module.vpc.public_subnet_ids
}

output "vpc_id" {
  value = module.vpc.vpc_id
}

Module Sources

Source typeExample
Local path./modules/vpc
Terraform Registryterraform-aws-modules/vpc/aws
GitHubgit::https://github.com/org/repo.git//modules/vpc
Git with taggit::https://github.com/org/repo.git?ref=v2.0.0
S3 buckets3::https://s3.amazonaws.com/bucket/module.zip

Using Registry Modules

The Terraform Registry hosts community-maintained modules. The terraform-aws-modules organisation is particularly well-maintained:

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "my-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
}

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 20.0"

  cluster_name    = "my-cluster"
  cluster_version = "1.30"
  vpc_id          = module.vpc.vpc_id
  subnet_ids      = module.vpc.private_subnets
}

Module Versioning Best Practices

  • Always pin module versions with version = "~> x.y"
  • Run terraform init -upgrade to update to the latest allowed version
  • For local modules in a monorepo, use relative paths
  • Document all module inputs and outputs with description fields

Next: Backends and Remote State — how to configure where Terraform stores state for production team workflows.

Key Takeaways

  • A module is any directory with .tf files — the root configuration is itself a module.
  • Child modules are called with a module block, passing input variables.
  • Modules output values that parent configurations can reference.
  • The Terraform Registry hosts thousands of community modules for common patterns.
  • Source local modules with relative paths, registry modules with registry URLs, Git repos with git::.

Test your knowledge

Try exam-style practice questions to reinforce what you've learned.

Practice Questions →