Terraform Modules IaC DRY

Terraform Modules: Building Reusable Infrastructure Components

KL
Kevin Lee
Platform Engineer
Aug 05, 2025
16 min read

What You'll Learn

How to keep Terraform DRY (Don't Repeat Yourself) using Modules. Learn how to structure, create, and consume Terraform modules with variables and outputs.

What is a Terraform Module?

A module is a container for multiple resources that are used together. Every Terraform configuration has at least one module, known as its root module, which consists of the resources defined in the `.tf` files in the main working directory.

A child module is a module that is called by another module. This allows you to encapsulate and reuse infrastructure logic (e.g., creating an S3 bucket with all the correct company security policies attached).

Creating a Custom Module

Let's create a reusable module for deploying a static website S3 bucket.

modules/static-website/main.tf
resource "aws_s3_bucket" "website" {
  bucket = var.bucket_name
  
  tags = merge(
    var.tags,
    {
      Module = "static-website"
    }
  )
}

resource "aws_s3_bucket_website_configuration" "website" {
  bucket = aws_s3_bucket.website.id

  index_document {
    suffix = "index.html"
  }
  error_document {
    key = "error.html"
  }
}

resource "aws_s3_bucket_public_access_block" "public_access" {
  bucket = aws_s3_bucket.website.id

  block_public_acls       = false
  block_public_policy     = false
  ignore_public_acls      = false
  restrict_public_buckets = false
}

resource "aws_s3_bucket_policy" "public_read" {
  bucket = aws_s3_bucket.website.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid       = "PublicReadGetObject"
        Effect    = "Allow"
        Principal = "*"
        Action    = "s3:GetObject"
        Resource  = "${aws_s3_bucket.website.arn}/*"
      },
    ]
  })
  
  depends_on = [aws_s3_bucket_public_access_block.public_access]
}
modules/static-website/variables.tf
variable "bucket_name" {
  description = "Name of the S3 bucket"
  type        = string
}

variable "tags" {
  description = "Tags to apply"
  type        = map(string)
  default     = {}
}
modules/static-website/outputs.tf
output "website_endpoint" {
  description = "The URL of the website"
  value       = aws_s3_bucket_website_configuration.website.website_endpoint
}

output "bucket_arn" {
  value = aws_s3_bucket.website.arn
}

Consuming the Module

Now in your root main.tf, you can call this module multiple times to create multiple websites with consistent security settings.

main.tf (Root)
module "frontend_app" {
  source = "./modules/static-website"

  bucket_name = "my-awesome-frontend-app-prod"
  tags = {
    Environment = "prod"
    Team        = "Frontend"
  }
}

module "marketing_site" {
  source = "./modules/static-website"

  bucket_name = "marketing-landing-page-prod"
  tags = {
    Environment = "prod"
    Team        = "Marketing"
  }
}

# Output the URL from the module!
output "frontend_url" {
  value = module.frontend_app.website_endpoint
}

Using Public Registry Modules

You don't always have to write your own modules! The Terraform Registry has thousands of community-verified modules (e.g., for creating complex AWS VPCs or EKS clusters).

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

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

  enable_nat_gateway = true
}

Keep Reading

D
DevOps

Docker Networking Demystified: Bridge, Host & Overlay

8 min read Read More
C
Cloud

AWS IAM Roles vs Users vs Policies

10 min read Read More
P
Programming

Understanding Python's GIL & Multiprocessing

14 min read Read More