AWS CloudFormation, an Infrastructure as Code service, includes a template made up of nine sections. Although made up of nine sections, the Resources section is the only one required. For this project we will be using Mappings, Resources, and Outputs.
Mappings
The Mappings section is basically a lookup table using key: value relationships. In this template the Amazon Machine Image is being mapped to its respective Region. For this project, I am using only have two regions mapped so the template is restricted to these two regions and would fail if launched outside of these regions.
Mappings:
RegionMap:
us-east-1:
"AMI": "ami-04902260ca3d33422"
us-west-2:
"AMI": "ami-0142f6ace1c558c7d"
Resources
The Resources section includes all the AWS resources that you want to create in the stack.
- For VPC, the template is assigning a CIDR of 10.0.0.0/16 and a tag of Name: LUIT Project.
- For InternetGateway, the template is creating the IGW and assigning a tag of Name: LUIT Project.
- For InternetGatewayAttachment, the template is attaching the IGW to the VPC. This is the first time in the template that we are using the intrinsic function Ref, which returns the value of the specified parameter or resource.
Resources:
VPC:
Type: 'AWS::EC2::VPC'
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: 'true'
EnableDnsHostnames: 'true'
Tags:
- Key: Name
Value: LUIT Project
InternetGateway:
Type: 'AWS::EC2::InternetGateway'
Properties:
Tags:
- Key: Name
Value: LUIT Project
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
4. For PublicSubnet1, the template is creating a public subnet.
- For VpdId it’s using the Ref function to reference the VPC.
- For AvailabilityZone it’s using the Select function to select a single object from a list. In this case it is returning the first Availability Zone using the GetAZ function.
- CidrBlock assigns this subnet a CIDR of 10.0.1.0/24
- MapPublicIpOnLaunch assigns a public IP on launch when set to true
- Tags assigns a tag to this subnet.
5. For PublicSubnet2, the template everything is similar to PublicSubnet1 except for:
- AvailabilityZone the template is now selecting the second AZ using the GetAZ function.
- CidrBlock is set to 10.0.2.0/24
- Tags
6. PrivateSubnet1 & PrivateSubnet2 follow the same structure as the public subnets with some differences.
- AvailabilityZones will match respectively with their public counterparts. This ensures we have a public and private subnet in the same AZ.
- CidrBlock will be different to ensure the subnets don’t overlap. I’ve chosen to use 10.0.11.0/24 and 10.0.12.0/24 to clearly differentiate the public subnets CIDR from the Private CIDR.
- MapPublicIpOnLaunch is set to false. Since these are private subnets we don’t want to assign a public IP.
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [ 0, !GetAZs '' ]
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: LUIT Project Public Subnet (AZ1)
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [ 1, !GetAZs '' ]
CidrBlock: 10.0.2.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: LUIT Project Public Subnet (AZ2)
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [ 0, !GetAZs '' ]
CidrBlock: 10.0.11.0/24
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: LUIT Project Private Subnet (AZ1)
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [ 1, !GetAZs '' ]
CidrBlock: 10.0.12.0/24
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: LUIT Project Private Subnet (AZ2)
7. For PublicRouteTable, the template is creating a public route table named LUIT Project Public Routes referencing VPC for the VpcId.
8. For DefaultPublicRoute, the template is creating a route to the Internet Gateway for 0.0.0.0/0.
- This is the first time in the template we use the DependsOn attribute. This specifies that the resource should follow the creation of another. In this case, the DefaultPublicRoute should follow the creation of the PublicRouteTable.
- RouteTableId references the PublicRouteTable.
- The GatewayId references the InternetGateway.
9. For PublicSubnet1RouteTableAssociation, the template is associating PublicSubnet1 to the PublicRouteTable.
10. For PublicSubnet2RouteTableAssociation, the template is associating PublicSubnet2 to the PublicRouteTable.
You’ll notice that the template has not created a default route table or associated the two private subnets. This is because a default route table is created with the VPC and all subnets are automatically associated unless otherwise specified. For this reason we don’t need to add them to the template since its the default.
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: LUIT Project Public Routes
DefaultPublicRoute:
Type: AWS::EC2::Route
DependsOn: InternetGatewayAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet1
PublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet2
11. For WebserverSecurityGroup, the template is creating a security group with an inbound rule allowing all traffic on HTTP.
WebServerSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: Enable HTTP from 0.0.0.0/0
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '80'
ToPort: '80'
CidrIp: 0.0.0.0/0
VpcId: !Ref VPC
12. For EC2Instance, the template is creating a single EC2 Instance with the following properties:
- ImageID: You could directly put in the AMI-Id for this property, but since we want the ability to use the template in two regions we use the FindInMap function to reference our Mappings.
- UserData: This section is used to bootstrap an instance. This particular template is running an update, installing an Apache web server and creating a simple web page. It uses the Sub function to update the AWS::Region pseudo parameter for the actual region the stack is created in. It uses the Base64 function to pass encoded data to EC2 instances.
EC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI]
InstanceType: t2.micro
SubnetId: !Ref PublicSubnet1
SecurityGroupIds:
- !Ref WebServerSecurityGroup
Tags:
- Key: Name
Value: LUIT Project EC2
UserData:
Fn::Base64: !Sub |
#!/bin/bash
yum install httpd -y
service httpd start
echo "<html><body><h1>Hello Cloud Gurus from Region ${AWS::Region}<h1></body></html>" > /var/www/html/index.html
Outputs
The Outputs section prints information to the Outputs tab in CloudFormation after the stack is created and can also be used to import information into other stacks.
Outputs:
PublicIp:
Description: EC2 Instance Public Ip
Value: !GetAtt EC2Instance.PublicIp
Uploading file to CloudFormation:
- Login to AWS and navigate to CloudFormation.
- Select Create stack and then select ‘Create template in Designer’ option.

3. Upload your template code in the space provide using yaml or json format and validate for any error. If every thing is okay you will be navigated to create your stack name and keep following the steps by leaving everything as default. You should end up in the review stage as shown below.

4. Next step in to click on create stack button and you should see as below.

5. Final result is to check webpage view:

Thank you for reading the article. I recommend you to review the AWS CloudFormation User Guide. This resource has been crucial for me while diving into CloudFormation.
Complete CloudFormation Template
The full source code on Github link.
AWSTemplateFormatVersion: 2010-09-09
Description: >-
This template deploys a VPC in either us-east1 or us-west-2, with a pair of public and private subnets spread
across two Availability Zones. It deploys an internet gateway, with a default
route on the public subnets, a Linux t2.micro Instance with Security Group with open HTTP access.
Mappings:
RegionMap:
us-east-1:
"AMI": "ami-04902260ca3d33422"
us-west-2:
"AMI": "ami-0142f6ace1c558c7d"
Resources:
VPC:
Type: 'AWS::EC2::VPC'
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: 'true'
EnableDnsHostnames: 'true'
Tags:
- Key: Name
Value: LUIT Project
InternetGateway:
Type: 'AWS::EC2::InternetGateway'
Properties:
Tags:
- Key: Name
Value: LUIT Project
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [ 0, !GetAZs '' ]
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: LUIT Project Public Subnet (AZ1)
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [ 1, !GetAZs '' ]
CidrBlock: 10.0.2.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: LUIT Project Public Subnet (AZ2)
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [ 0, !GetAZs '' ]
CidrBlock: 10.0.11.0/24
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: LUIT Project Private Subnet (AZ1)
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [ 1, !GetAZs '' ]
CidrBlock: 10.0.12.0/24
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: LUIT Project Private Subnet (AZ2)
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: LUIT Project Public Routes
DefaultPublicRoute:
Type: AWS::EC2::Route
DependsOn: InternetGatewayAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet1
PublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet2
WebServerSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: Enable HTTP from 0.0.0.0/0
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '80'
ToPort: '80'
CidrIp: 0.0.0.0/0
VpcId: !Ref VPC
EC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI]
InstanceType: t2.micro
SubnetId: !Ref PublicSubnet1
SecurityGroupIds:
- !Ref WebServerSecurityGroup
Tags:
- Key: Name
Value: LUIT Project EC2
UserData:
Fn::Base64: !Sub |
#!/bin/bash
yum install httpd -y
service httpd start
echo "<html><body><h1>Hello Cloud Gurus from Region ${AWS::Region}<h1></body></html>" > /var/www/html/index.html
Outputs:
PublicIp:
Description: EC2 Instance Public Ip
Value: !GetAtt EC2Instance.PublicIp