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.
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]
}
variable "bucket_name" {
description = "Name of the S3 bucket"
type = string
}
variable "tags" {
description = "Tags to apply"
type = map(string)
default = {}
}
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.
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
}