Skip to main content

Lab: CloudFormation

Objective​

Create CloudFormation templates to deploy a complete infrastructure in a reproducible manner.

Part 1: Basic Template​

Exercise 1.1: Security Group​

Create a template for a Security Group allowing HTTP and SSH:

AWSTemplateFormatVersion: "2010-09-09"
Description: Security Group for web server

Resources:
WebServerSG:
Type: _______________
Properties:
GroupDescription: _______________
SecurityGroupIngress:
- IpProtocol: ___
FromPort: ___
ToPort: ___
CidrIp: _______________
- IpProtocol: ___
FromPort: ___
ToPort: ___
CidrIp: _______________
💡 Solution
AWSTemplateFormatVersion: "2010-09-09"
Description: Security Group for web server

Resources:
WebServerSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow HTTP and SSH access
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0

Exercise 1.2: Adding Parameters​

Enhance the template with parameters:

Parameters:
SSHLocation:
Type: _______________
Default: _______________
Description: IP range for SSH access
AllowedPattern: _______________
ConstraintDescription: Must be valid CIDR

Environment:
Type: String
AllowedValues:
- ___
- ___
- ___
💡 Solution
Parameters:
SSHLocation:
Type: String
Default: 0.0.0.0/0
Description: IP range for SSH access
AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
ConstraintDescription: Must be valid CIDR (e.g., 10.0.0.0/24)

Environment:
Type: String
Default: dev
AllowedValues:
- dev
- staging
- prod
Description: Environment type

Then in the Security Group:

SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref SSHLocation

Part 2: EC2 Instance​

Exercise 2.1: Complete EC2 Template​

AWSTemplateFormatVersion: "2010-09-09"
Description: Web Server with EC2

Parameters:
Environment:
Type: String
Default: dev
AllowedValues: [dev, staging, prod]

InstanceType:
Type: String
Default: t3.micro
AllowedValues: [t3.micro, t3.small, t3.medium]

KeyName:
Type: AWS::EC2::KeyPair::KeyName
Description: EC2 Key Pair

Mappings:
RegionAMI:
us-east-1:
AMI: ami-0c55b159cbfafe1f0
eu-west-1:
AMI: ami-0d729a60

Resources:
# Complete the resources
WebServerSG:
Type: _______________
# ...

WebServer:
Type: _______________
Properties:
ImageId: _______________
InstanceType: _______________
KeyName: _______________
SecurityGroupIds:
- _______________
Tags:
- Key: Name
Value: _______________
UserData:
Fn::Base64: |
#!/bin/bash
# Complete the script

Outputs:
PublicIP:
Value: _______________
WebURL:
Value: _______________
💡 Complete Solution
AWSTemplateFormatVersion: "2010-09-09"
Description: Web Server with EC2

Parameters:
Environment:
Type: String
Default: dev
AllowedValues: [dev, staging, prod]

InstanceType:
Type: String
Default: t3.micro
AllowedValues: [t3.micro, t3.small, t3.medium]

KeyName:
Type: AWS::EC2::KeyPair::KeyName
Description: EC2 Key Pair

Mappings:
RegionAMI:
us-east-1:
AMI: ami-0c55b159cbfafe1f0
eu-west-1:
AMI: ami-0d729a60

Resources:
WebServerSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow HTTP and SSH
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub "${Environment}-web-sg"
- Key: Environment
Value: !Ref Environment

WebServer:
Type: AWS::EC2::Instance
Properties:
ImageId: !FindInMap [RegionAMI, !Ref "AWS::Region", AMI]
InstanceType: !Ref InstanceType
KeyName: !Ref KeyName
SecurityGroupIds:
- !Ref WebServerSG
Tags:
- Key: Name
Value: !Sub "${Environment}-web-server"
- Key: Environment
Value: !Ref Environment
UserData:
Fn::Base64: !Sub |
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Environment: ${Environment}</h1>" > /var/www/html/index.html
echo "<p>Instance Type: ${InstanceType}</p>" >> /var/www/html/index.html

Outputs:
InstanceId:
Description: Instance ID
Value: !Ref WebServer

PublicIP:
Description: Public IP
Value: !GetAtt WebServer.PublicIp

WebURL:
Description: Website URL
Value: !Sub "http://${WebServer.PublicIp}"

Part 3: Complete VPC​

Exercise 3.1: VPC Architecture​

Create a template for this architecture:

┌─────────────────────────────────────────────────────────────┐
│ VPC 10.0.0.0/16 │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Public Subnet │ │ Private Subnet │ │
│ │ 10.0.1.0/24 │ │ 10.0.2.0/24 │ │
│ │ │ │ │ │
│ │ ┌───────────┐ │ │ ┌───────────┐ │ │
│ │ │ EC2 │ │ │ │ EC2 │ │ │
│ │ │ (web) │ │ │ │ (app) │ │ │
│ │ └───────────┘ │ │ └───────────┘ │ │
│ │ │ │ │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

Template to complete:

Resources:
# VPC
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: _______________
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-vpc"

# Internet Gateway
InternetGateway:
Type: _______________

AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: _______________
InternetGatewayId: _______________

# Public Subnet
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: _______________
CidrBlock: _______________
MapPublicIpOnLaunch: ___
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-public"

# Private Subnet
PrivateSubnet:
Type: _______________
# Complete...

# Route Table Public
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC

PublicRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: _______________
DestinationCidrBlock: _______________
GatewayId: _______________

PublicSubnetRouteAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: _______________
RouteTableId: _______________
💡 Solution
AWSTemplateFormatVersion: "2010-09-09"
Description: VPC with public and private subnets

Parameters:
Environment:
Type: String
Default: dev

Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-vpc"

InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-igw"

AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway

PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: true
AvailabilityZone: !Select [0, !GetAZs ""]
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-public"

PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
MapPublicIpOnLaunch: false
AvailabilityZone: !Select [0, !GetAZs ""]
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-private"

PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-public-rt"

PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway

PublicSubnetRouteAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable

Outputs:
VPCId:
Value: !Ref VPC
Export:
Name: !Sub "${AWS::StackName}-VPCId"

PublicSubnetId:
Value: !Ref PublicSubnet
Export:
Name: !Sub "${AWS::StackName}-PublicSubnetId"

PrivateSubnetId:
Value: !Ref PrivateSubnet
Export:
Name: !Sub "${AWS::StackName}-PrivateSubnetId"

Part 4: Troubleshooting​

Exercise 4.1: Identify Errors​

This template contains errors. Find them:

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
InstanceType:
Type: string # Error 1
Default: t3.micro

Resources:
MyInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-12345678
InstanceType: !Ref instancetype # Error 2
SecurityGroups:
- !Ref MySG # Error 3 (order)
Tags:
- Key: Name
Value: !Sub ${AWS:StackName}-instance # Error 4

MySG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription # Error 5
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
💡 Solutions
  1. Error 1: string → String (uppercase)
  2. Error 2: instancetype → InstanceType (match parameter case)
  3. Error 3: MySG must be defined before or use DependsOn
  4. Error 4: AWS:StackName → AWS::StackName (double colons)
  5. Error 5: Missing : "Description" after GroupDescription

Corrected Template:

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
InstanceType:
Type: String
Default: t3.micro

Resources:
MySG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "Security group for web server"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0

MyInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-12345678
InstanceType: !Ref InstanceType
SecurityGroupIds:
- !Ref MySG
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-instance"

Part 5: Final Project​

Exercise 5.1: Complete Infrastructure​

Create a template for the following architecture:

┌─────────────────────────────────────────────────────────────┐
│ 3-Tier Web Architecture │
├─────────────────────────────────────────────────────────────┤
│ │
│ Internet → ALB → EC2 (Auto Scaling) → RDS │
│ │
│ Required components: │
│ • VPC with 2 public and 2 private subnets │
│ • Security Groups (ALB, EC2, RDS) │
│ • Application Load Balancer │
│ • Launch Template + Auto Scaling Group │
│ • RDS MySQL (optional) │
│ │
│ Parameters: │
│ • Environment (dev/staging/prod) │
│ • InstanceType │
│ • MinSize, MaxSize, DesiredCapacity │
│ │
└─────────────────────────────────────────────────────────────┘

Validation Criteria​

CriterionPoints
Valid template (no errors)/15
Well-defined parameters/15
Properly configured VPC/20
Restrictive Security Groups/15
Functional ALB + ASG/20
Useful outputs/10
Documentation/comments/5
Total/100

Final Deliverable​

Document to Produce​

  1. Complete CloudFormation template (YAML)
  2. Deployment instructions (CLI commands)
  3. Architecture diagram
  4. List of parameters and values
  5. Validation tests performed

End of the course! Good luck on your AWS journey.