Building a Basic AWS VPC Network with CloudFormation

BalajiBalaji
4 min read

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

  1. Log in to AWS Console.

  2. Switch to the eu-north-1 region.

  3. Open CloudFormation → Click Create stack → With new resources (standard).

  4. Upload template file → Select network.yml.

  5. Give your stack a name, e.g., basic-network-stack.

  6. Click NextNextCreate stack.

  7. 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.

0
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!