AWS

AWS VPC Architecture Explained with Diagrams — From Simple to Production

By Raghvendra Pandey · April 2026 · 9 min read

Every AWS architecture starts with a VPC. Whether you're deploying a single EC2 instance or a fleet of microservices on EKS, the network foundation determines your security posture, availability, and cost. Yet VPC networking is one of the most commonly misunderstood parts of AWS.

This guide walks through four VPC architecture patterns, from simplest to production-grade, with Terraform code for each. You can paste any of these into InfraSketch to see the architecture diagram.

Pattern 1: Single public subnet (development only)

The simplest possible setup — one VPC, one public subnet, one internet gateway. Every resource gets a public IP and is directly accessible from the internet.

resource "aws_vpc" "dev" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  tags = { Name = "dev-vpc" }
}

resource "aws_internet_gateway" "dev" {
  vpc_id = aws_vpc.dev.id
}

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.dev.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "ap-south-1a"
  map_public_ip_on_launch = true
  tags = { Name = "public-subnet" }
}

resource "aws_instance" "app" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
  subnet_id     = aws_subnet.public.id
  tags = { Name = "app-server" }
}

This pattern works for development environments, quick prototypes, and personal projects where you need something running fast without worrying about network security. Everything is on a public subnet, which means every resource is exposed to the internet.

Never use this pattern in production. Databases, application servers, and internal services should never be on public subnets. It's like putting your house keys under the doormat — technically works, but invites trouble.

Pattern 2: Public + private subnets with NAT

The standard two-tier architecture. Public subnets host load balancers and bastion hosts. Private subnets host application servers and databases. A NAT Gateway allows private resources to access the internet (for updates, API calls) without being directly accessible.

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  tags = { Name = "main-vpc" }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
}

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "ap-south-1a"
  map_public_ip_on_launch = true
  tags = { Name = "public-subnet" }
}

resource "aws_subnet" "private" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.10.0/24"
  availability_zone = "ap-south-1a"
  tags = { Name = "private-subnet" }
}

resource "aws_eip" "nat" {
  domain = "vpc"
}

resource "aws_nat_gateway" "main" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public.id
  tags = { Name = "main-nat" }
}

resource "aws_lb" "app" {
  name               = "app-alb"
  internal           = false
  load_balancer_type = "application"
  subnets            = [aws_subnet.public.id]
}

resource "aws_instance" "app" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.medium"
  subnet_id     = aws_subnet.private.id
  tags = { Name = "app-server" }
}

resource "aws_db_instance" "main" {
  identifier     = "app-db"
  engine         = "postgres"
  instance_class = "db.t3.medium"
  tags = { Name = "app-database" }
}

This is the most common VPC pattern and works well for small to medium applications. The ALB in the public subnet handles incoming traffic and forwards it to application servers in the private subnet. The database sits in the private subnet with no public access.

The NAT Gateway costs around $32/month (in ap-south-1) plus data transfer charges. For development environments where you want to save money, you can use a NAT Instance (a small EC2 instance configured as a NAT) instead, though it's less reliable.

Pattern 3: Multi-AZ for high availability

Pattern 2 has a single point of failure — everything is in one Availability Zone. If that AZ goes down (and they do, occasionally), your entire application is offline. Production workloads need multi-AZ redundancy.

resource "aws_vpc" "prod" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  tags = { Name = "prod-vpc" }
}

resource "aws_internet_gateway" "prod" {
  vpc_id = aws_vpc.prod.id
}

# Public subnets in two AZs
resource "aws_subnet" "public_1" {
  vpc_id            = aws_vpc.prod.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "ap-south-1a"
  map_public_ip_on_launch = true
  tags = { Name = "public-1a" }
}

resource "aws_subnet" "public_2" {
  vpc_id            = aws_vpc.prod.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "ap-south-1b"
  map_public_ip_on_launch = true
  tags = { Name = "public-1b" }
}

# Private subnets in two AZs
resource "aws_subnet" "private_1" {
  vpc_id            = aws_vpc.prod.id
  cidr_block        = "10.0.10.0/24"
  availability_zone = "ap-south-1a"
  tags = { Name = "private-1a" }
}

resource "aws_subnet" "private_2" {
  vpc_id            = aws_vpc.prod.id
  cidr_block        = "10.0.11.0/24"
  availability_zone = "ap-south-1b"
  tags = { Name = "private-1b" }
}

# NAT Gateways - one per AZ for redundancy
resource "aws_eip" "nat_1" { domain = "vpc" }
resource "aws_eip" "nat_2" { domain = "vpc" }

resource "aws_nat_gateway" "az1" {
  allocation_id = aws_eip.nat_1.id
  subnet_id     = aws_subnet.public_1.id
}

resource "aws_nat_gateway" "az2" {
  allocation_id = aws_eip.nat_2.id
  subnet_id     = aws_subnet.public_2.id
}

# ALB spans both public subnets
resource "aws_lb" "prod" {
  name               = "prod-alb"
  internal           = false
  load_balancer_type = "application"
  subnets            = [aws_subnet.public_1.id, aws_subnet.public_2.id]
}

# EKS cluster spans both private subnets
resource "aws_eks_cluster" "prod" {
  name     = "prod-cluster"
  role_arn = aws_iam_role.eks.arn
  vpc_config {
    subnet_ids = [aws_subnet.private_1.id, aws_subnet.private_2.id]
  }
}

resource "aws_iam_role" "eks" {
  name = "eks-cluster-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = { Service = "eks.amazonaws.com" }
    }]
  })
}

# RDS with Multi-AZ enabled
resource "aws_db_instance" "prod" {
  identifier     = "prod-db"
  engine         = "postgres"
  instance_class = "db.r5.large"
  multi_az       = true
}

This pattern duplicates subnets and NAT Gateways across two Availability Zones. The ALB automatically distributes traffic across both AZs. EKS schedules pods across both private subnets. RDS Multi-AZ maintains a standby replica in the second AZ.

The cost increase from Pattern 2 is primarily the second NAT Gateway (~$32/month) and the additional EKS/RDS capacity in the second AZ. For production workloads, this cost is negligible compared to the downtime risk of a single-AZ deployment.

Paste any of these Terraform snippets into InfraSketch to see the architecture diagram generated automatically. You can visually verify your VPC layout matches your intent.

Pattern 4: Production-grade with security layers

The complete production pattern adds security groups, VPC endpoints, CloudFront CDN, and a WAF layer. This is what a well-architected AWS deployment actually looks like.

The full Terraform for this pattern is extensive — typically 500+ lines. The key additions beyond Pattern 3 are security groups with least-privilege rules for each service tier, VPC endpoints for S3 and ECR to keep traffic within the AWS network and reduce NAT costs, a CloudFront distribution in front of the ALB for caching and DDoS protection, a WAF (Web Application Firewall) attached to CloudFront or the ALB for Layer 7 filtering, and VPC Flow Logs sent to CloudWatch for network monitoring and incident investigation.

Each of these additions addresses a specific security or operational concern. Security groups control which services can communicate with each other — the database should only accept connections from the application tier, not from the entire VPC. VPC endpoints eliminate the need for traffic to S3 and ECR to traverse the public internet through the NAT Gateway, which both improves security and reduces NAT data transfer costs.

Common VPC mistakes to avoid

After managing VPCs for dozens of production workloads, these are the mistakes I see most often.

The first is using too-small CIDR blocks. A /24 VPC gives you 256 IPs, which sounds like plenty until you realize subnets, load balancers, and EKS each consume IPs. Start with a /16 (65,536 IPs) for production VPCs. You can always use smaller subnets within it.

The second is putting databases on public subnets. It happens more often than you'd think, usually because someone copied a tutorial that took shortcuts. Databases should always be on private subnets with security groups that only allow connections from the application tier.

The third is using a single NAT Gateway for production. NAT Gateways are zonal — if the AZ goes down, all private subnet internet access in that AZ is lost. Always use one NAT Gateway per AZ in production.

The fourth is overlapping CIDR blocks with other VPCs or on-premises networks. If you ever need VPC peering or a VPN connection, overlapping CIDRs will prevent it. Plan your IP addressing scheme across all environments before you deploy anything.

Visualize your VPC

Understanding your VPC architecture is much easier with a diagram. Take any of the Terraform snippets above, paste them into InfraSketch, and you'll see the resources grouped by category — networking resources (VPC, subnets, IGW, NAT) in one group, compute (EKS, EC2) in another, databases (RDS) in a third.

This visual grouping makes it immediately obvious whether your architecture follows the patterns described here — are databases in private subnets? Is there a NAT Gateway in each AZ? Is the ALB spanning multiple public subnets? A quick diagram answers these questions faster than reading through Terraform files.

See your VPC architecture

Paste your VPC Terraform code and visualize the network architecture instantly.

Generate VPC Diagram