A reusable, production-grade Terraform module for a multi-AZ AWS VPC: public and private subnets spread across availability zones, NAT gateway egress (single shared or one-per-AZ), and optional VPC flow logs to CloudWatch — the baseline network layer most AWS workloads are built on top of.
Every team ends up writing "the VPC module" at least once. This is a clean, tested, opinionated-but-configurable version of that module: sane defaults, explicit trade-offs (cost vs. resilience for NAT), and CI that actually validates the Terraform rather than just checking it parses.
┌─────────────────────────┐
│ Internet Gateway │
└────────────┬─────────────┘
│
┌──────────────────────────────────────────────────────┐
│ VPC (10.0.0.0/16) │
│ │
│ AZ-a AZ-b AZ-c │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ Public │ │ Public │ │ Public │ │
│ │ subnet │ │ subnet │ │ subnet │ │
│ └───┬────┘ └───┬────┘ └───┬────┘ │
│ │ NAT GW │ NAT GW │ NAT GW │
│ ┌───┴────┐ ┌───┴────┐ ┌───┴────┐ │
│ │ Private│ │ Private│ │ Private│ │
│ │ subnet │ │ subnet │ │ subnet │ │
│ └────────┘ └────────┘ └────────┘ │
└──────────────────────────────────────────────────────────┘
│
▼
VPC Flow Logs → CloudWatch Logs (optional)
- Public subnets route
0.0.0.0/0via the Internet Gateway and auto-assign public IPs. - Private subnets route
0.0.0.0/0via NAT Gateway(s) — either one per AZ (resilient, default) or a single shared one (single_nat_gateway = true, cheaper, single point of failure). - Flow logs are optional and ship to a CloudWatch Log Group with configurable retention.
module "vpc" {
source = "github.com/MU5A/terraform-aws-vpc-module"
name = "prod"
cidr_block = "10.0.0.0/16"
az_count = 3
enable_nat_gateway = true
single_nat_gateway = false # one NAT per AZ for HA
enable_flow_logs = true
flow_log_retention_days = 30
tags = {
Environment = "production"
Team = "platform"
}
}See examples/simple for a complete, runnable example.
| Name | Description | Type | Default |
|---|---|---|---|
name |
Name prefix applied to all resources | string |
— |
cidr_block |
CIDR block for the VPC | string |
"10.0.0.0/16" |
az_count |
Number of AZs to spread subnets across (2–6) | number |
3 |
public_subnet_newbits |
Extra prefix bits for public subnet sizing | number |
8 |
private_subnet_newbits |
Extra prefix bits for private subnet sizing | number |
8 |
enable_nat_gateway |
Provision NAT gateway(s) for private egress | bool |
true |
single_nat_gateway |
Share a single NAT gateway instead of one per AZ | bool |
false |
enable_flow_logs |
Enable VPC flow logs to CloudWatch | bool |
true |
flow_log_retention_days |
CloudWatch Logs retention for flow logs | number |
30 |
tags |
Additional tags applied to all resources | map(string) |
{} |
| Name | Description |
|---|---|
vpc_id |
ID of the created VPC |
vpc_cidr_block |
CIDR block of the created VPC |
internet_gateway_id |
ID of the internet gateway |
public_subnet_ids |
IDs of the public subnets |
private_subnet_ids |
IDs of the private subnets |
nat_gateway_ids |
IDs of the provisioned NAT gateways |
public_route_table_id |
ID of the shared public route table |
private_route_table_ids |
IDs of the private route tables |
availability_zones |
Availability zones used by this module |
This module ships with a native Terraform test suite (terraform >= 1.7) under
tests/, using a mocked aws provider so it runs without real AWS
credentials — this is exactly how CI runs it:
terraform init -backend=false
terraform testCI additionally runs terraform fmt -check, terraform validate, and tflint
on every push and pull request.
| Name | Version |
|---|---|
| terraform | >= 1.7.0 |
| aws provider | ~> 5.0 |