Production-ready Terraform module patterns for AWS, Azure, GCP, and OCI infrastructure.
Create reusable, well-tested Terraform modules for common cloud infrastructure patterns across multiple cloud providers.
- Build reusable infrastructure components
- Standardize cloud resource provisioning
- Implement infrastructure as code best practices
- Create multi-cloud compatible modules
- Establish organizational Terraform standards
terraform-modules/
├── aws/
│ ├── vpc/
│ ├── eks/
│ ├── rds/
│ └── s3/
├── azure/
│ ├── vnet/
│ ├── aks/
│ └── storage/
├── gcp/
│ ├── vpc/
│ ├── gke/
│ └── cloud-sql/
└── oci/
├── vcn/
├── oke/
└── object-storage/
module-name/
├── main.tf # Main resources
├── variables.tf # Input variables
├── outputs.tf # Output values
├── versions.tf # Provider versions
├── README.md # Documentation
├── examples/ # Usage examples
│ └── complete/
│ ├── main.tf
│ └── variables.tf
└── tests/ # Terratest files
└── module_test.go
main.tf:
resource "aws_vpc" "main" {
cidr_block = var.cidr_block
enable_dns_hostnames = var.enable_dns_hostnames
enable_dns_support = var.enable_dns_support
tags = merge(
{
Name = var.name
},
var.tags
)
}
resource "aws_subnet" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
tags = merge(
{
Name = "${var.name}-private-${count.index + 1}"
Tier = "private"
},
var.tags
)
}
resource "aws_internet_gateway" "main" {
count = var.create_internet_gateway ? 1 : 0
vpc_id = aws_vpc.main.id
tags = merge(
{
Name = "${var.name}-igw"
},
var.tags
)
}
Keep the internet gateway toggle off by default so the gateway is opt in, enable VPC flow logs by default, and leave the default security group with no rules.
variables.tf:
variable "name" {
description = "Name of the VPC"
type = string
}
variable "cidr_block" {
description = "CIDR block for VPC"
type = string
validation {
condition = can(regex("^([0-9]{1,3}\\.){3}[0-9]{1,3}/[0-9]{1,2}$", var.cidr_block))
error_message = "CIDR block must be valid IPv4 CIDR notation."
}
}
variable "availability_zones" {
description = "List of availability zones"
type = list(string)
}
variable "private_subnet_cidrs" {
description = "CIDR blocks for private subnets"
type = list(string)
default = []
}
variable "enable_dns_hostnames" {
description = "Enable DNS hostnames in VPC"
type = bool
default = true
}
variable "tags" {
description = "Additional tags"
type = map(string)
default = {}
}
The DNS support and internet gateway toggles used in main.tf must also be declared here as boolean inputs, with DNS support on and the internet gateway off by default. Provide at least as many availability zones as private subnet CIDR blocks, since each subnet is matched to a zone by position. The CIDR pattern above only checks the shape of the value, so confirm that the block is a real IPv4 range.
outputs.tf:
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "private_subnet_ids" {
description = "IDs of private subnets"
value = aws_subnet.private[*].id
}
output "vpc_cidr_block" {
description = "CIDR block of VPC"
value = aws_vpc.main.cidr_block
}
- Use semantic versioning for modules
- Document all variables with descriptions
- Provide examples in examples/ directory
- Use validation blocks for input validation
- Output important attributes for module composition
- Pin provider versions in versions.tf, commit the dependency lock file, pin remote module sources to a released version, tag or commit, and pin Go test dependencies to fixed versions with their checksum file committed
- Use locals for computed values
- Implement conditional resources with count/for_each
- Test modules with Terratest
- Tag all resources consistently
- Use secure defaults for data modules: database modules default to encrypted storage, deletion protection and private access; storage bucket modules default to blocked public access, default encryption with KMS keys and versioning; expose each default as a variable users can override
Reference: See references/aws-modules.md and references/oci-modules.md
module "vpc" {
source = "../../modules/aws/vpc"
name = "production"
cidr_block = "10.0.0.0/16"
availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
private_subnet_cidrs = [
"10.0.1.0/24",
"10.0.2.0/24",
"10.0.3.0/24"
]
tags = {
Environment = "production"
ManagedBy = "terraform"
}
}
module "rds" {
source = "../../modules/aws/rds"
identifier = "production-db"
engine = "postgres"
engine_version = "15.3"
instance_class = "db.t3.large"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
tags = {
Environment = "production"
}
}
These values are illustrative. Try them first in a dedicated sandbox account, and send production changes through a reviewed plan with an approval step before apply.
Database secrets: keep the master password out of configuration and variable files. Let the cloud provider secret service manage it, or read it from an existing secret, and mark password variables and outputs as sensitive. Enable storage encryption and deletion protection for the database.
Engine version: the version shown is only an example. Choose a currently supported and patched release before use and keep minor versions up to date.
State: in root configurations such as this one, state files can hold secrets in plain text. Store state in an encrypted remote backend with locking and least privilege access, and keep state files, the local terraform working directory and secret variable files out of version control. Reusable child modules intentionally leave out backend blocks.
Caution: this test creates real, billable cloud resources. Run it only after the user explicitly confirms, and only in a dedicated sandbox account with narrowly scoped, temporary credentials. Review a terraform plan first, use unique resource names, and if a run is interrupted, check for and clean up any leftover resources.
// tests/vpc_test.go
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestVPCModule(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../examples/complete",
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
vpcID := terraform.Output(t, terraformOptions, "vpc_id")
assert.NotEmpty(t, vpcID)
}
multi-cloud-architecture - For architectural decisions
cost-optimization - For cost-effective designs