⏱ Duration: 5 Hours
📚 Learning Objectives
- Understand VPC concepts and architecture
- Create VPCs and subnets
- Configure route tables and internet gateways
- Set up security groups and NACLs
- Build a complete VPC with public/private subnets
📖 Core Concepts (2 Hours)
What is VPC?
Amazon Virtual Private Cloud (VPC) lets you provision a logically isolated section of the AWS Cloud where you can launch resources in a virtual network you define.
- VPC: Your private network in AWS
- Subnets: Segments within a VPC (public/private)
- Route Tables: Direct network traffic
- Internet Gateway: Connect VPC to internet
- NAT Gateway: Allow private subnets to access internet
VPC Architecture
┌─────────────────────────────────────────────────────────────┐
│ AWS Region (us-east-1) │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ VPC (10.0.0.0/16) │ │
│ │ ┌──────────────┐ │ │
│ │ │ Internet │ │ │
│ │ │ Gateway │ │ │
│ │ └──────┬───────┘ │ │
│ │ │ │ │
│ │ ┌────────────────────────────┼───────────────────┐ │ │
│ │ │ Public Subnet (10.0.1.0/24) │ │ │
│ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │
│ │ │ │ EC2 │ │ NAT │ │ ALB │ │ │ │
│ │ │ │ (Bastion)│ │ Gateway │ │ │ │ │ │
│ │ │ └──────────┘ └────┬─────┘ └──────────┘ │ │ │
│ │ └───────────────────────┼────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌───────────────────────┼────────────────────────┐ │ │
│ │ │ Private Subnet (10.0.2.0/24) │ │ │
│ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │
│ │ │ │ EC2 │ │ EC2 │ │ RDS │ │ │ │
│ │ │ │ (App) │ │ (App) │ │ │ │ │ │
│ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
CIDR Notation
CIDR = Classless Inter-Domain Routing
Format: IP_ADDRESS/PREFIX_LENGTH
Examples:
10.0.0.0/16 = 10.0.0.0 - 10.0.255.255 (65,536 IPs)
10.0.0.0/24 = 10.0.0.0 - 10.0.0.255 (256 IPs)
10.0.0.0/28 = 10.0.0.0 - 10.0.0.15 (16 IPs)
VPC CIDR Guidelines:
├── Allowed: /16 to /28
├── Recommended: /16 for flexibility
├── Cannot overlap with other VPCs if peered
└── AWS reserves 5 IPs per subnet:
.0 = Network address
.1 = VPC router
.2 = DNS
.3 = Future use
.255 = Broadcast
Security: Security Groups vs NACLs
┌─────────────────────┬─────────────────────┐
│ Security Groups │ NACLs │
├─────────────────────┼─────────────────────┤
│ Instance level │ Subnet level │
│ Stateful │ Stateless │
│ Allow rules only │ Allow & Deny rules │
│ All rules evaluated │ Rules in order │
│ Must be associated │ Auto-applies │
└─────────────────────┴─────────────────────┘
Stateful (SG): Return traffic auto-allowed
Stateless (NACL): Must explicitly allow return traffic
Best Practice: Use both for defense in depth
VPC Components Summary
Component Description
─────────────────────────────────────────────────────
VPC Virtual network container
Subnet IP range within VPC (AZ-specific)
Internet Gateway Connects VPC to internet
NAT Gateway Outbound internet for private subnets
Route Table Rules for routing traffic
Security Group Instance-level firewall (stateful)
NACL Subnet-level firewall (stateless)
Elastic IP Static public IP address
VPC Peering Connect VPCs together
VPN Gateway VPN connection to on-premises
🔬 Hands-on Lab (2.5 Hours)
Lab 1: Create VPC with Subnets
- Create a custom VPC
- Create public and private subnets
# Create VPC
VPC_ID=$(aws ec2 create-vpc \
--cidr-block 10.0.0.0/16 \
--tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=devops-vpc}]' \
--query 'Vpc.VpcId' --output text)
echo "VPC ID: $VPC_ID"
# Enable DNS hostnames
aws ec2 modify-vpc-attribute \
--vpc-id $VPC_ID \
--enable-dns-hostnames
# Create public subnet in AZ-a
PUBLIC_SUBNET=$(aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block 10.0.1.0/24 \
--availability-zone us-east-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=devops-public-subnet}]' \
--query 'Subnet.SubnetId' --output text)
echo "Public Subnet: $PUBLIC_SUBNET"
# Create private subnet in AZ-a
PRIVATE_SUBNET=$(aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block 10.0.2.0/24 \
--availability-zone us-east-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=devops-private-subnet}]' \
--query 'Subnet.SubnetId' --output text)
echo "Private Subnet: $PRIVATE_SUBNET"
# Enable auto-assign public IP for public subnet
aws ec2 modify-subnet-attribute \
--subnet-id $PUBLIC_SUBNET \
--map-public-ip-on-launch
Lab 2: Configure Internet Gateway
- Create and attach internet gateway
- Configure route table for public subnet
# Create Internet Gateway
IGW_ID=$(aws ec2 create-internet-gateway \
--tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=devops-igw}]' \
--query 'InternetGateway.InternetGatewayId' --output text)
echo "Internet Gateway: $IGW_ID"
# Attach to VPC
aws ec2 attach-internet-gateway \
--internet-gateway-id $IGW_ID \
--vpc-id $VPC_ID
# Create public route table
PUBLIC_RT=$(aws ec2 create-route-table \
--vpc-id $VPC_ID \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=devops-public-rt}]' \
--query 'RouteTable.RouteTableId' --output text)
echo "Public Route Table: $PUBLIC_RT"
# Add route to internet gateway
aws ec2 create-route \
--route-table-id $PUBLIC_RT \
--destination-cidr-block 0.0.0.0/0 \
--gateway-id $IGW_ID
# Associate public subnet with public route table
aws ec2 associate-route-table \
--route-table-id $PUBLIC_RT \
--subnet-id $PUBLIC_SUBNET
Lab 3: Configure NAT Gateway
- Create NAT Gateway for private subnet
- Configure route table for private subnet
# Allocate Elastic IP for NAT Gateway
EIP_ALLOC=$(aws ec2 allocate-address \
--domain vpc \
--tag-specifications 'ResourceType=elastic-ip,Tags=[{Key=Name,Value=devops-nat-eip}]' \
--query 'AllocationId' --output text)
echo "Elastic IP Allocation: $EIP_ALLOC"
# Create NAT Gateway in public subnet
NAT_GW=$(aws ec2 create-nat-gateway \
--subnet-id $PUBLIC_SUBNET \
--allocation-id $EIP_ALLOC \
--tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=devops-nat}]' \
--query 'NatGateway.NatGatewayId' --output text)
echo "NAT Gateway: $NAT_GW"
# Wait for NAT Gateway to be available
echo "Waiting for NAT Gateway..."
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT_GW
# Create private route table
PRIVATE_RT=$(aws ec2 create-route-table \
--vpc-id $VPC_ID \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=devops-private-rt}]' \
--query 'RouteTable.RouteTableId' --output text)
echo "Private Route Table: $PRIVATE_RT"
# Add route to NAT Gateway
aws ec2 create-route \
--route-table-id $PRIVATE_RT \
--destination-cidr-block 0.0.0.0/0 \
--nat-gateway-id $NAT_GW
# Associate private subnet with private route table
aws ec2 associate-route-table \
--route-table-id $PRIVATE_RT \
--subnet-id $PRIVATE_SUBNET
Lab 4: Create Security Groups
- Create security group for public instances
- Create security group for private instances
# Create public security group (bastion/web)
PUBLIC_SG=$(aws ec2 create-security-group \
--group-name devops-public-sg \
--description "Public subnet security group" \
--vpc-id $VPC_ID \
--query 'GroupId' --output text)
echo "Public Security Group: $PUBLIC_SG"
# Add rules to public security group
aws ec2 authorize-security-group-ingress \
--group-id $PUBLIC_SG \
--protocol tcp --port 22 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id $PUBLIC_SG \
--protocol tcp --port 80 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id $PUBLIC_SG \
--protocol tcp --port 443 --cidr 0.0.0.0/0
# Create private security group (app servers)
PRIVATE_SG=$(aws ec2 create-security-group \
--group-name devops-private-sg \
--description "Private subnet security group" \
--vpc-id $VPC_ID \
--query 'GroupId' --output text)
echo "Private Security Group: $PRIVATE_SG"
# Allow SSH only from public security group
aws ec2 authorize-security-group-ingress \
--group-id $PRIVATE_SG \
--protocol tcp --port 22 \
--source-group $PUBLIC_SG
# Allow app traffic from public subnet
aws ec2 authorize-security-group-ingress \
--group-id $PRIVATE_SG \
--protocol tcp --port 8080 \
--source-group $PUBLIC_SG
# List security group rules
aws ec2 describe-security-groups \
--group-ids $PUBLIC_SG $PRIVATE_SG \
--query 'SecurityGroups[*].[GroupName,IpPermissions]'
Lab 5: Launch Instances and Test
- Launch instance in public subnet
- Launch instance in private subnet
- Test connectivity
# Create key pair
aws ec2 create-key-pair \
--key-name vpc-key \
--query 'KeyMaterial' --output text > vpc-key.pem
chmod 400 vpc-key.pem
# Get latest Amazon Linux 2023 AMI
AMI_ID=$(aws ec2 describe-images \
--owners amazon \
--filters "Name=name,Values=al2023-ami-2023*-x86_64" \
"Name=state,Values=available" \
--query 'Images | sort_by(@, &CreationDate) | [-1].ImageId' \
--output text)
# Launch public instance (bastion)
PUBLIC_INSTANCE=$(aws ec2 run-instances \
--image-id $AMI_ID \
--instance-type t2.micro \
--key-name vpc-key \
--subnet-id $PUBLIC_SUBNET \
--security-group-ids $PUBLIC_SG \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=bastion-host}]' \
--query 'Instances[0].InstanceId' --output text)
# Launch private instance
PRIVATE_INSTANCE=$(aws ec2 run-instances \
--image-id $AMI_ID \
--instance-type t2.micro \
--key-name vpc-key \
--subnet-id $PRIVATE_SUBNET \
--security-group-ids $PRIVATE_SG \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=app-server}]' \
--query 'Instances[0].InstanceId' --output text)
# Wait for instances
aws ec2 wait instance-running --instance-ids $PUBLIC_INSTANCE $PRIVATE_INSTANCE
# Get IPs
PUBLIC_IP=$(aws ec2 describe-instances \
--instance-ids $PUBLIC_INSTANCE \
--query 'Reservations[0].Instances[0].PublicIpAddress' --output text)
PRIVATE_IP=$(aws ec2 describe-instances \
--instance-ids $PRIVATE_INSTANCE \
--query 'Reservations[0].Instances[0].PrivateIpAddress' --output text)
echo "Bastion Public IP: $PUBLIC_IP"
echo "App Server Private IP: $PRIVATE_IP"
# Test: SSH to bastion, then to private instance
# First, copy key to bastion
scp -i vpc-key.pem vpc-key.pem ec2-user@$PUBLIC_IP:~/
# SSH to bastion
ssh -i vpc-key.pem ec2-user@$PUBLIC_IP
# From bastion, SSH to private instance
# ssh -i vpc-key.pem ec2-user@PRIVATE_IP
# Test internet from private instance
# curl -s http://checkip.amazonaws.com
Lab 6: Clean Up All Resources
# Terminate instances
aws ec2 terminate-instances --instance-ids $PUBLIC_INSTANCE $PRIVATE_INSTANCE
aws ec2 wait instance-terminated --instance-ids $PUBLIC_INSTANCE $PRIVATE_INSTANCE
# Delete NAT Gateway
aws ec2 delete-nat-gateway --nat-gateway-id $NAT_GW
echo "Waiting for NAT Gateway deletion..."
sleep 60
# Release Elastic IP
aws ec2 release-address --allocation-id $EIP_ALLOC
# Delete subnets
aws ec2 delete-subnet --subnet-id $PUBLIC_SUBNET
aws ec2 delete-subnet --subnet-id $PRIVATE_SUBNET
# Delete route tables (disassociate first if needed)
aws ec2 delete-route-table --route-table-id $PUBLIC_RT
aws ec2 delete-route-table --route-table-id $PRIVATE_RT
# Detach and delete internet gateway
aws ec2 detach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID
aws ec2 delete-internet-gateway --internet-gateway-id $IGW_ID
# Delete security groups
aws ec2 delete-security-group --group-id $PUBLIC_SG
aws ec2 delete-security-group --group-id $PRIVATE_SG
# Delete VPC
aws ec2 delete-vpc --vpc-id $VPC_ID
# Delete key pair
aws ec2 delete-key-pair --key-name vpc-key
rm vpc-key.pem
echo "Cleanup complete!"
✅ Day 5 Checklist
- Understand VPC components and architecture
- Can create VPCs and subnets
- Can configure internet and NAT gateways
- Can set up route tables
- Understand security groups vs NACLs
- Built complete VPC with public/private subnets