Why VPC Design Matters
The VPC is the foundation of every AWS deployment. A poorly designed VPC creates security risks, limits scalability, and makes future changes painful. Getting it right from the start โ with Terraform โ means your network is reproducible, auditable, and consistent across all environments.
Production VPC Architecture
A production-grade VPC typically uses a three-tier architecture across three Availability Zones:
- Public subnets โ load balancers, NAT gateways, bastion hosts
- Private subnets โ application servers, container workloads
- Database subnets โ RDS instances, ElastiCache, isolated from internet
Core VPC Terraform Resources
locals {
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
private_subnets = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
db_subnets = ["10.0.21.0/24", "10.0.22.0/24", "10.0.23.0/24"]
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = { Name = "${var.project}-vpc" }
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = { Name = "${var.project}-igw" }
}
# Public Subnets
resource "aws_subnet" "public" {
count = length(local.azs)
vpc_id = aws_vpc.main.id
cidr_block = local.public_subnets[count.index]
availability_zone = local.azs[count.index]
map_public_ip_on_launch = true
tags = { Name = "${var.project}-public-${count.index + 1}" }
}
# Private Subnets
resource "aws_subnet" "private" {
count = length(local.azs)
vpc_id = aws_vpc.main.id
cidr_block = local.private_subnets[count.index]
availability_zone = local.azs[count.index]
tags = { Name = "${var.project}-private-${count.index + 1}" }
}
NAT Gateways
Private subnet resources need NAT gateways to reach the internet for software updates and API calls. For high availability, deploy one NAT gateway per AZ:
resource "aws_eip" "nat" {
count = length(local.azs)
domain = "vpc"
tags = { Name = "${var.project}-nat-eip-${count.index + 1}" }
}
resource "aws_nat_gateway" "main" {
count = length(local.azs)
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = { Name = "${var.project}-nat-${count.index + 1}" }
depends_on = [aws_internet_gateway.main]
}
resource "aws_route_table" "private" {
count = length(local.azs)
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[count.index].id
}
}
Security Groups
resource "aws_security_group" "alb" {
name = "${var.project}-alb-sg"
description = "Application Load Balancer"
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "app" {
name = "${var.project}-app-sg"
description = "Application servers"
vpc_id = aws_vpc.main.id
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
}
VPC Flow Logs
Enable VPC Flow Logs for security analysis and troubleshooting:
resource "aws_flow_log" "main" {
vpc_id = aws_vpc.main.id
traffic_type = "ALL"
iam_role_arn = aws_iam_role.flow_logs.arn
log_destination = aws_cloudwatch_log_group.flow_logs.arn
}
resource "aws_cloudwatch_log_group" "flow_logs" {
name = "/aws/vpc/flow-logs/${var.project}"
retention_in_days = 30
}
VPC Endpoints (Save NAT Gateway Costs)
For S3 and DynamoDB access, use VPC Gateway Endpoints โ they're free and route traffic within the AWS network:
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = aws_route_table.private[*].id
}
resource "aws_vpc_endpoint" "dynamodb" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.dynamodb"
vpc_endpoint_type = "Gateway"
route_table_ids = aws_route_table.private[*].id
}
Outputs
output "vpc_id" { value = aws_vpc.main.id }
output "public_subnet_ids" { value = aws_subnet.public[*].id }
output "private_subnet_ids" { value = aws_subnet.private[*].id }
output "nat_gateway_ips" { value = aws_eip.nat[*].public_ip }
Try CloudFormation Free
Design infrastructure visually and generate production-ready Terraform in seconds โ 693 resources across 9 cloud providers.
๐ Open the Designer โ