Building a Basic AWS VPC Network with CloudFormation


Deploying a VPC, Subnets, Internet Gateway, and NAT Gateway in eu-north-1
When building infrastructure on AWS, the very first step is often to set up your network — a VPC with public and private subnets, Internet access for public resources, and secure routing for private workloads.
In this post, we’ll create a basic AWS network architecture using a CloudFormation template that is ready to deploy in the eu-north-1
(Stockholm) region.
Architecture Overview
Here’s what we’re going to create:
VPC with a
/16
CIDR range (10.0.0.0/16
)4 Subnets (
/24
each):Public Subnet 1 (AZ
eu-north-1a
)Public Subnet 2 (AZ
eu-north-1b
)Private Subnet 1 (AZ
eu-north-1a
)Private Subnet 2 (AZ
eu-north-1b
)
Internet Gateway (IGW) attached to VPC for public subnets
NAT Gateway in Public Subnet 1 for private subnet outbound Internet access
Public Route Table associated with public subnets
Private Route Table associated with private subnets
This is a best practice starting point for deploying applications with both public-facing and private workloads.
CloudFormation Template
Below is the YAML CloudFormation template for our setup.
Save this as network.yml
.
AWSTemplateFormatVersion: 2010-09-09
Description: Basic AWS Network - VPC, Subnets, IGW, NAT Gateway (eu-north-1)
Parameters:
VpcCidr:
Type: String
Default: 10.0.0.0/16
Description: CIDR block for the VPC
PublicSubnet1Cidr:
Type: String
Default: 10.0.1.0/24
PublicSubnet2Cidr:
Type: String
Default: 10.0.2.0/24
PrivateSubnet1Cidr:
Type: String
Default: 10.0.3.0/24
PrivateSubnet2Cidr:
Type: String
Default: 10.0.4.0/24
Resources:
# VPC
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: BasicVPC
# Internet Gateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: BasicIGW
AttachIGW:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
# Public Subnet 1 (eu-north-1a)
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PublicSubnet1Cidr
MapPublicIpOnLaunch: true
AvailabilityZone: eu-north-1a # Based on the location change AZ
Tags:
- Key: Name
Value: PublicSubnet1
# Public Subnet 2 (eu-north-1b)
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PublicSubnet2Cidr
MapPublicIpOnLaunch: true
AvailabilityZone: eu-north-1b # Based on the location change AZ
Tags:
- Key: Name
Value: PublicSubnet2
# Private Subnet 1 (eu-north-1a)
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PrivateSubnet1Cidr
AvailabilityZone: eu-north-1a # Based on the location change AZ
Tags:
- Key: Name
Value: PrivateSubnet1
# Private Subnet 2 (eu-north-1b)
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PrivateSubnet2Cidr
AvailabilityZone: eu-north-1b # Based on the location change AZ
Tags:
- Key: Name
Value: PrivateSubnet2
# Elastic IP for NAT Gateway
NatEIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
# NAT Gateway (in eu-north-1a Public Subnet 1)
NatGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatEIP.AllocationId
SubnetId: !Ref PublicSubnet1
Tags:
- Key: Name
Value: BasicNAT
# Public Route Table
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: PublicRT
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachIGW
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet1RTAssoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1
RouteTableId: !Ref PublicRouteTable
PublicSubnet2RTAssoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet2
RouteTableId: !Ref PublicRouteTable
# Private Route Table
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: PrivateRT
PrivateRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway
PrivateSubnet1RTAssoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet1
RouteTableId: !Ref PrivateRouteTable
PrivateSubnet2RTAssoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet2
RouteTableId: !Ref PrivateRouteTable
Outputs:
VpcId:
Value: !Ref VPC
PublicSubnets:
Value: !Join [",", [!Ref PublicSubnet1, !Ref PublicSubnet2]]
PrivateSubnets:
Value: !Join [",", [!Ref PrivateSubnet1, !Ref PrivateSubnet2]]
How to Deploy in AWS CloudFormation
1. Using AWS Management Console
Log in to AWS Console.
Switch to the
eu-north-1
region.Open CloudFormation → Click Create stack → With new resources (standard).
Upload template file → Select
network.yml
.Give your stack a name, e.g.,
basic-network-stack
.Click Next → Next → Create stack.
Wait for CREATE_COMPLETE status.
2. Using AWS CLI
aws cloudformation create-stack \
--region eu-north-1 \
--stack-name basic-network-stack \
--template-body file://network.yml \
--capabilities CAPABILITY_NAMED_IAM
Check stack status:
aws cloudformation describe-stacks \
--region eu-north-1 \
--stack-name basic-network-stack
Testing the Network
Once deployed:
Public subnets can launch EC2 instances with public IPs and reach the Internet.
Private subnets can launch EC2 instances without public IPs, still able to reach the Internet via the NAT Gateway.
Cleaning Up
To avoid charges (NAT Gateway is billed hourly + data processed):
aws cloudformation delete-stack \
--region eu-north-1 \
--stack-name basic-network-stack
Conclusion
This setup gives you a secure, scalable baseline network in AWS:
Public subnets for web servers, ALBs, or Bastion hosts.
Private subnets for databases, application servers, or internal services.
Managed entirely through Infrastructure as Code with CloudFormation.
In future posts, we can expand this with VPC endpoints, security groups, and EKS/ECS integration.
Subscribe to my newsletter
Read articles from Balaji directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Balaji
Balaji
👋 Hi there! I'm Balaji S, a passionate technologist with a focus on AWS, Linux, DevOps, and Kubernetes. 💼 As an experienced DevOps engineer, I specialize in designing, implementing, and optimizing cloud infrastructure on AWS. I have a deep understanding of various AWS services like EC2, S3, RDS, Lambda, and more, and I leverage my expertise to architect scalable and secure solutions. 🐧 With a strong background in Linux systems administration, I'm well-versed in managing and troubleshooting Linux-based environments. I enjoy working with open-source technologies and have a knack for maximizing performance and stability in Linux systems. ⚙️ DevOps is my passion, and I thrive in bridging the gap between development and operations teams. I automate processes, streamline CI/CD pipelines, and implement robust monitoring and logging solutions to ensure continuous delivery and high availability of applications. ☸️ Kubernetes is a key part of my toolkit, and I have hands-on experience in deploying and managing containerized applications in Kubernetes clusters. I'm skilled in creating Helm charts, optimizing resource utilization, and implementing effective scaling strategies for microservices architectures. 📝 On Hashnode, I share my insights, best practices, and tutorials on topics related to AWS, Linux, DevOps, and Kubernetes. Join me on my journey as we explore the latest trends and advancements in cloud-native technologies. ✨ Let's connect and dive into the world of AWS, Linux, DevOps, and Kubernetes together!