Skip to main content

Network Foundation with TERRAFORM...!!!

Updated 8 min readMarkdown
Part of seriesTerraform and our infrastructure

Day 1 — The vpc Module: Laying the Network Foundation

Series: Zero to Production on AWS EKS with Terraform — Part 3 · Day 1 Layer 1 of 5 — the foundation. Module of the day: vpc.

Last updated: 26 October 2026

In short: The vpc Terraform module builds the private network an EKS platform stands on: a multi-AZ VPC with public and private subnets, NAT gateways, route tables and optional VPC endpoints. Workloads live in private subnets, only load balancers face the internet, and its outputs feed every module above it.

In Day 0 we built the mental model; in "The Final Picture" we saw the whole platform. Now we start building it for real — bottom up. And the bottom of everything is the network.

No network, no cluster. No cluster, no apps. So Day 1 is the vpc module.


What is a VPC?

A VPC (Virtual Private Cloud) is your own private slice of AWS's data center. Think of it as renting an empty office building:

  • It has a street address range (the CIDR, e.g. 10.0.0.0/16) — the pool of internal IP addresses everything inside will use.
  • It's split into rooms (subnets) — some with a public door to the street (public subnets), some locked away inside (private subnets).
  • It has a front door (Internet Gateway) and a one-way outbound exit (NAT Gateway) so people inside can call out, but strangers can't walk in.

Everything we build in the rest of this series lives inside this building.


What does the vpc module create?

One call creates a multi-AZ VPC with public and private subnets, an Internet Gateway, NAT gateways, route tables and optional VPC endpoints. Our module wraps the battle-tested terraform-aws-modules/vpc/aws and adds the AWS-endpoint and tagging conventions our platform expects. One call gives you:

  • A VPC with the CIDR you choose.
  • Public + private subnets, spread across multiple Availability Zones (different physical buildings — so one AZ failing doesn't take you down).
  • An Internet Gateway for the public subnets.
  • NAT Gateway(s) so private workloads can reach the internet outbound (pull container images, call APIs) without being reachable inbound.
  • Route tables wiring it all together.
  • VPC Endpoints (optional) — private on-ramps to AWS services (S3, ECR, STS, EC2, ELB, CloudWatch Logs) so that traffic never leaves AWS's backbone. The S3 gateway endpoint is especially worth it: it's free and saves NAT charges on image layers.

Why do multiple AZs and private subnets matter?

Multi-AZ gives you resilience and private subnets give you security.

  • Multi-AZ = resilience. Your nodes and load balancers are spread across 2–3 zones.
  • Private subnets for workloads = security. Your Kubernetes nodes have no public IP. The only thing the internet can reach is the load balancer you explicitly create.

Which inputs does the vpc module need?

Per environment you mainly set the name, CIDR, AZs, subnet ranges, cluster name, NAT strategy and endpoints. The module exposes a focused set of variables. The ones you'll actually set per-environment:

Variable What it controls Example
vpc_name Name tag for the VPC "prod-vpc"
vpc_cidr The whole IP range "10.0.0.0/16"
availability_zones Which AZs to span ["us-east-1a","us-east-1b"]
private_subnet_cidrs Ranges for private rooms ["10.0.1.0/24","10.0.2.0/24"]
public_subnet_cidrs Ranges for public rooms ["10.0.101.0/24","10.0.102.0/24"]
cluster_name Used to tag subnets for EKS "prod-cluster"
enable_nat_gateway / single_nat_gateway / one_nat_gateway_per_az Outbound internet strategy & cost true / false / true
enable_s3_gateway_endpoint Free private S3 access true
enable_ecr_api_endpoint, enable_ecr_dkr_endpoint, enable_sts_endpoint, … Private on-ramps to AWS services true
enable_flow_logs Record accepted/rejected traffic to CloudWatch — gold for audits and "why can't A reach B?" debugging true
environment, tags Env label + standard tags "prod"

How does subnet tagging help EKS load balancers?

Subnet tags tell the ALB controller where to place public and internal load balancers. The module tags subnets with Kubernetes discovery tags:

  • kubernetes.io/role/elb → "put public load balancers here"
  • kubernetes.io/role/internal-elb → "put internal load balancers here"

You set this now, on Day 1, and the ALB controller two layers up auto-discovers it. The foundation quietly sets up the floors above it.


What does the vpc module output?

It outputs the VPC ID, subnet IDs, route table IDs, NAT gateway IDs and the endpoint security group. These outputs are the contract — they become the inputs of every module above:

Output Used by Why
vpc_id eks, security-group, tgw "which network am I in?"
private_subnets eks (node placement), tgw where nodes & attachments live
public_subnets public load balancers where internet-facing LBs go
private_route_table_ids tgw / tgw-attachment to add cross-network routes
natgw_ids reference/debugging the outbound exits
vpc_endpoint_sg_id endpoint traffic rules firewall for the private on-ramps

Remember the contract from "The Final Picture": vpc.outputs.private_subnets → eks.subnet_ids. That single wire is why Terraform knows to build the VPC before the cluster.


What does a real vpc module call look like?

Here is how a consumer stack uses the module:

module "vpc" {
  source = "git::https://github.com/<org>/devops-terraform-modules.git//modules/vpc?ref=v1.4.0"

  vpc_name             = "prod-vpc"
  vpc_cidr             = "10.0.0.0/16"
  cluster_name         = "prod-cluster"
  availability_zones   = ["us-east-1a", "us-east-1b"]
  private_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnet_cidrs  = ["10.0.101.0/24", "10.0.102.0/24"]

  enable_nat_gateway     = true
  one_nat_gateway_per_az = true     # resilient (1 NAT per AZ) — costs more
  single_nat_gateway     = false

  enable_s3_gateway_endpoint = true
  enable_ecr_api_endpoint    = true
  enable_ecr_dkr_endpoint    = true

  environment = "prod"
  tags        = { Project = "platform", ManagedBy = "terraform" }
}

What are the vpc module gotchas?

The big ones are CIDR sizing, the NAT cost-vs-resilience choice, endpoint savings and keeping workloads out of public subnets.

  • CIDR sizing is forever-ish. Resizing a VPC later is painful. Pick a /16 and carve sensible /24s. Leave room to grow.
  • single_nat_gateway vs one_nat_gateway_per_az is a cost-vs-resilience knob. Dev → single NAT (cheap). Prod → one per AZ (no cross-AZ data charges, survives an AZ outage).
  • Endpoints save real money. If your nodes pull a lot of images, the ECR + S3 endpoints pay for themselves by cutting NAT data-processing fees.
  • Don't put workloads in public subnets. Public subnets are only for load balancers and NAT. Everything else goes private.

How do you verify the VPC was created?

Check the VPC CIDR and the subnet spread with the AWS CLI after terraform apply:

# the VPC exists with the CIDR you set
aws ec2 describe-vpcs --filters Name=tag:Name,Values=prod-vpc \
  --query 'Vpcs[].CidrBlock'

# subnets landed across AZs
aws ec2 describe-subnets --filters Name=vpc-id,Values=<vpc_id> \
  --query 'Subnets[].[AvailabilityZone,CidrBlock,MapPublicIpOnLaunch]' --output table

Frequently asked questions

Do I need NAT gateways for a private EKS cluster?

Yes, if private workloads need outbound internet access, for example to pull container images or call APIs. NAT gateways let private subnets reach out without being reachable inbound.

Should I use a single NAT gateway or one per AZ?

It's a cost-vs-resilience choice. A single NAT gateway is cheaper and fine for dev; one NAT gateway per AZ suits prod because it avoids cross-AZ data charges and survives an AZ outage.

Why add an S3 gateway endpoint to a VPC?

The S3 gateway endpoint is free and keeps S3 traffic on AWS's backbone. It cuts NAT data-processing fees, especially when nodes pull a lot of image layers.

What CIDR size should I pick for an EKS VPC?

Pick a /16 and carve sensible /24 subnets, leaving room to grow. Resizing a VPC later is painful, so CIDR sizing is effectively permanent.

Which subnet tags does the AWS Load Balancer Controller use?

It uses kubernetes.io/role/elb for public load balancers and kubernetes.io/role/internal-elb for internal ones. The vpc module sets these tags so the controller can auto-discover subnets.


What's the takeaway?

  • The vpc module gives you a resilient, multi-AZ private network in one call.
  • Workloads live in private subnets; only load balancers face the internet.
  • Its outputs (vpc_id, private_subnets, route tables) are the inputs everything else depends on.
  • The subnet tags you set today are what makes load-balancing "just work" on Day 5.

Next — Day 2: we connect things. Firewalls with the security-group module, and bridging separate networks with tgw / tgw-attachment. 🔌

About the author

  • DevOps & Platform Engineer | AWS · Azure · GCP · Terraform · GitOps · SRE | Building reliable, cost-efficient cloud platforms

No comments yet. Be the first to comment.

Terraform and our infrastructure

Part 3 of 10

An end-to-end, hands-on guide to designing, building, and deploying production-grade cloud infrastructure using Terraform. Learn how to create reusable Terraform modules, compose them into complete infrastructure stacks, manage multiple environments, implement networking, compute, databases, security, IAM, remote state, and secrets management, and automate deployments with CI/CD pipelines. From individual modules to fully functional, scalable, secure, and maintainable production infrastructure, this series covers the complete journey using real-world architecture and best practices.

Up nextFirewalls and Bridges with Terraform..!Day 2 — security-group, tgw & tgw-attachment: Firewalls and Bridges Series: Zero to Production on AWS EKS with Terraform — Part 4 · Day 2 Layer 1 of 5 — still the foundation. Modules of the day: secu