Skip to main content

Overview

The VPC module creates a complete AWS VPC infrastructure with public and private subnets across multiple availability zones, NAT gateways for outbound internet access, and optional VPC endpoints for S3 and DynamoDB.

Features

Multi-AZ Support

Deploy subnets across multiple availability zones for high availability

Public & Private Subnets

Separate public subnets with internet access and private subnets with NAT

NAT Gateway Options

Single or multiple NAT gateways for cost vs. availability tradeoff

VPC Endpoints

Optional S3 and DynamoDB endpoints to reduce NAT costs

Flow Logs

Optional VPC flow logs for network monitoring and troubleshooting

EKS Integration

Automatic subnet tagging for AWS Load Balancer Controller

Architecture

Multi-AZ with High Availability

┌─────────────────────────────────────────────────────────────────┐
│                    VPC (10.0.0.0/16)                          │
│                                                                │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐ │
│  │  AZ-1 (us-e-1a)  │  │  AZ-2 (us-e-1b)  │  │  AZ-3 (us-e-1c)  │ │
│  │                 │  │                 │  │                 │ │
│  │  ┌───────────┐  │  │  ┌───────────┐  │  │  ┌───────────┐  │ │
│  │  │  Public    │  │  │  │  Public    │  │  │  │  Public    │  │ │
│  │  │  Subnet    │  │  │  │  Subnet    │  │  │  │  Subnet    │  │ │
│  │  │ 10.0.1/24 │◄────│  │ 10.0.2/24 │◄────│  │ 10.0.3/24 │  │ │
│  │  └─────┬──────┘  │  │  └─────┬──────┘  │  │  └─────┬──────┘  │ │
│  │       │NAT      │  │       │NAT      │  │       │NAT      │ │
│  │  ┌─────┴──────┐  │  │  ┌─────┴──────┐  │  │  ┌─────┴──────┐  │ │
│  │  │  Private   │  │  │  │  Private   │  │  │  │  Private   │  │ │
│  │  │  Subnet    │  │  │  │  Subnet    │  │  │  │  Subnet    │  │ │
│  │  │ 10.0.11/24│  │  │  │ 10.0.12/24│  │  │  │ 10.0.13/24│  │ │
│  │  └───────────┘  │  │  └───────────┘  │  │  └───────────┘  │ │
│  │                 │  │                 │  │                 │ │
│  └─────────────────┘  └─────────────────┘  └─────────────────┘ │
│                                                                │
│                     Internet Gateway                          │
└─────────────────────────────────────────────────────────────────┘

Usage Examples

Basic Configuration

module "vpc" {
  source = "[email protected]:opsnorth/terraform-modules.git//vpc?ref=v1.0.0"

  vpc_name           = "my-vpc"
  vpc_cidr           = "10.0.0.0/16"
  aws_region         = "us-east-1"
  availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]

  public_subnet_cidrs  = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]

  enable_nat_gateway = true
  single_nat_gateway = false

  tags = {
    Environment = "production"
    Team        = "platform"
  }
}

Development Environment (Cost-Optimized)

module "vpc" {
  source = "[email protected]:opsnorth/terraform-modules.git//vpc?ref=v1.0.0"

  vpc_name           = "dev-vpc"
  vpc_cidr           = "10.0.0.0/16"
  aws_region         = "us-west-2"
  availability_zones = ["us-west-2a", "us-west-2b"]

  public_subnet_cidrs  = ["10.0.1.0/24", "10.0.2.0/24"]
  private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24"]

  enable_nat_gateway = true
  single_nat_gateway = true  # Single NAT for cost savings

  tags = {
    Environment = "development"
    ManagedBy   = "terraform"
  }
}
Using single_nat_gateway = true in development can save ~$90/month by using one NAT gateway instead of one per AZ.

Production Environment (High Availability)

module "vpc" {
  source = "[email protected]:opsnorth/terraform-modules.git//vpc?ref=v1.0.0"

  vpc_name           = "prod-vpc"
  vpc_cidr           = "10.0.0.0/16"
  aws_region         = "us-east-1"
  availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]

  public_subnet_cidrs  = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]

  enable_nat_gateway = true
  single_nat_gateway = false  # NAT gateway per AZ for HA

  # VPC endpoints to reduce NAT costs
  enable_s3_endpoint       = true
  enable_dynamodb_endpoint = true

  # Flow logs for monitoring
  enable_flow_logs             = true
  flow_logs_destination_type   = "cloud-watch-logs"
  flow_logs_destination_arn    = aws_cloudwatch_log_group.vpc_flow_logs.arn
  flow_logs_iam_role_arn       = aws_iam_role.vpc_flow_logs.arn

  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
    CostCenter  = "engineering"
  }
}

resource "aws_cloudwatch_log_group" "vpc_flow_logs" {
  name              = "/aws/vpc/flow-logs"
  retention_in_days = 30
}

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

EKS-Ready VPC

Automatically tag subnets for AWS Load Balancer Controller:
module "vpc" {
  source = "[email protected]:opsnorth/terraform-modules.git//vpc?ref=v1.0.0"

  vpc_name           = "eks-vpc"
  vpc_cidr           = "10.0.0.0/16"
  aws_region         = "us-east-1"
  availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]

  public_subnet_cidrs  = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]

  # Automatically adds kubernetes.io/cluster/<name> and role tags
  eks_cluster_name = "my-eks-cluster"

  enable_nat_gateway = true
  single_nat_gateway = false

  tags = {
    Environment = "production"
  }
}
When eks_cluster_name is set, the module adds:
  • kubernetes.io/cluster/<name> = shared to all subnets
  • kubernetes.io/role/elb = 1 to public subnets
  • kubernetes.io/role/internal-elb = 1 to private subnets

Minimal Configuration (Public Only)

For simple use cases without private subnets:
module "vpc" {
  source = "[email protected]:opsnorth/terraform-modules.git//vpc?ref=v1.0.0"

  vpc_name           = "simple-vpc"
  vpc_cidr           = "10.0.0.0/16"
  aws_region         = "us-east-1"
  availability_zones = ["us-east-1a", "us-east-1b"]

  public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]

  enable_nat_gateway = false  # No NAT needed without private subnets

  tags = {
    Environment = "test"
  }
}

Inputs

NameDescriptionTypeDefaultRequired
vpc_nameName to be used on all the resources as identifierstringn/ayes
vpc_cidrThe CIDR block for the VPCstringn/ayes
aws_regionAWS region where resources will be createdstringn/ayes
availability_zonesList of availability zones in which to create subnetslist(string)n/ayes
public_subnet_cidrsList of CIDR blocks for public subnetslist(string)[]no
private_subnet_cidrsList of CIDR blocks for private subnetslist(string)[]no
eks_cluster_nameOptional EKS cluster name used to tag subnets for Kubernetes load balancersstringnullno
enable_dns_hostnamesShould be true to enable DNS hostnames in the VPCbooltrueno
enable_dns_supportShould be true to enable DNS support in the VPCbooltrueno
create_igwControls if an Internet Gateway is created for public subnetsbooltrueno
map_public_ip_on_launchShould be true to auto-assign public IP on launchbooltrueno
enable_nat_gatewayShould be true if you want to provision NAT Gateways for private subnetsbooltrueno
single_nat_gatewayShould be true if you want to provision a single shared NAT Gatewayboolfalseno
enable_flow_logsWhether to enable VPC Flow Logsboolfalseno
flow_logs_destination_typeType of flow log destination (cloud-watch-logs or s3)string"cloud-watch-logs"no
flow_logs_destination_arnARN of the destination for VPC Flow Logsstring""no
flow_logs_iam_role_arnARN of the IAM role for VPC Flow Logsstring""no
flow_logs_traffic_typeType of traffic to capture (ACCEPT, REJECT, ALL)string"ALL"no
manage_default_security_groupShould be true to adopt and manage the default security groupbooltrueno
enable_s3_endpointShould be true if you want to provision an S3 VPC endpointboolfalseno
enable_dynamodb_endpointShould be true if you want to provision a DynamoDB VPC endpointboolfalseno
tagsA map of tags to add to all resourcesmap(string){}no

Outputs

NameDescription
vpc_idThe ID of the VPC
vpc_arnThe ARN of the VPC
vpc_cidr_blockThe CIDR block of the VPC
internet_gateway_idThe ID of the Internet Gateway
public_subnet_idsList of IDs of public subnets
public_subnet_arnsList of ARNs of public subnets
public_subnet_cidr_blocksList of CIDR blocks of public subnets
private_subnet_idsList of IDs of private subnets
private_subnet_arnsList of ARNs of private subnets
private_subnet_cidr_blocksList of CIDR blocks of private subnets
nat_gateway_idsList of NAT Gateway IDs
nat_eip_public_ipsList of public Elastic IPs created for NAT Gateways
public_route_table_idsList of IDs of public route tables
private_route_table_idsList of IDs of private route tables
vpc_flow_log_idThe ID of the VPC Flow Log
default_security_group_idThe ID of the default security group
vpc_endpoint_s3_idThe ID of the S3 VPC endpoint
vpc_endpoint_dynamodb_idThe ID of the DynamoDB VPC endpoint
availability_zonesList of availability zones used

Cost Considerations

Hourly Charges:
  • Each NAT Gateway: ~0.045/hour( 0.045/hour (~32/month)
  • Data processing: $0.045/GB
Recommendations:
  • Development: Use single_nat_gateway = true
  • Production: Use one NAT per AZ for HA
  • Consider VPC endpoints to reduce NAT data transfer
Gateway Endpoints (Free):
  • S3 and DynamoDB endpoints have no hourly charge
  • Saves NAT data processing costs
  • Enable in production to reduce costs
Interface Endpoints:
  • Not included in this module
  • ~0.01/hourperAZ( 0.01/hour per AZ (~7/month per endpoint)
  • NAT gateways include EIP allocation
  • No charge while attached to running NAT gateway
  • Small charge for unattached EIPs
  • CloudWatch Logs: Storage + ingestion costs
  • S3: Storage costs only
  • Consider retention policies to manage costs

Best Practices

CIDR Planning

Use a VPC CIDR that allows for growth. A /16 (65,536 IPs) is recommended for most use cases.

Multi-AZ

Always use at least 2 AZs for development and 3 AZs for production workloads.

Subnet Sizing

Public subnets can be smaller (/24 = 256 IPs). Private subnets should be larger for pod IP addresses.

NAT Strategy

Development: Single NAT. Production: One NAT per AZ for resilience.

VPC Endpoints

Enable S3 and DynamoDB endpoints in production to save on NAT costs.

Flow Logs

Enable flow logs in production for security monitoring and troubleshooting.

Troubleshooting

Subnet CIDR Conflicts

Error:
Error: Error creating subnet: InvalidSubnet.Conflict
Solution: Ensure subnet CIDRs don’t overlap and are within the VPC CIDR:
vpc_cidr = "10.0.0.0/16"

# Valid - within VPC CIDR and non-overlapping
public_subnet_cidrs  = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24"]

NAT Gateway Attachment Issues

Error:
Error: timeout while waiting for state to become 'available'
Solution: Ensure Internet Gateway is created and attached before NAT gateways. The module handles this dependency automatically.

EKS Subnet Discovery

If EKS can’t discover subnets:
  1. Verify eks_cluster_name matches your cluster name exactly
  2. Check that tags are present on subnets:
    aws ec2 describe-subnets --subnet-ids <subnet-id>
    
  3. Ensure both public and private subnets have appropriate role tags

Flow Logs Not Working

Common issues:
  • IAM role missing permissions
  • CloudWatch Log Group doesn’t exist
  • IAM role trust policy incorrect
Verify IAM role:
aws logs describe-log-groups --log-group-name-prefix /aws/vpc
aws iam get-role --role-name vpc-flow-logs-role

EKS Module

Deploy EKS cluster in this VPC

RDS Module

Create RDS instances in private subnets

EC2 Module

Launch EC2 instances in subnets

Usage Guide

Common patterns and best practices

Build docs developers (and LLMs) love