Skip to main content

AWS CloudFormation & DevTools Examples

This page contains real-world CloudFormation templates, infrastructure automation examples, and AWS Developer Tools (CodeDeploy, CodeCommit, CodePipeline) configurations extracted from hands-on labs and architectural patterns.

Related: For AWS interview preparation covering CloudFormation concepts and architecture, see the AWS Interview Questions page which includes Top 100 and Top 250 questions.

Architecture Overview

The templates and examples on this page build a complete three-tier architecture:


1. VPC Infrastructure

VPC CloudFormation Template

Creates a VPC with two public subnets across availability zones, with DNS enabled and Internet Gateway.

File: Automating Infrastructure Builds Using CloudFormation/vpc.yaml

Description: >
AWS CloudFormation Template that creates a VPC with DNS and public IPs
enabled and two public subnets. Exports VPC and public subnet IDs.

Parameters:
VpcCIDR:
Description: Please enter the CIDR for this VPC
Type: String
Default: 10.7.0.0/16

PublicSubnet1CIDR:
Description: Please enter the CIDR for the public subnet in the first Availability Zone
Type: String
Default: 10.7.1.0/24

PublicSubnet2CIDR:
Description: Please enter the CIDR for the public subnet in the second Availability Zone
Type: String
Default: 10.7.2.0/24

Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCIDR
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
-
Key: Name
Value: !Ref AWS::StackName

InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
-
Key: Name
Value: !Ref AWS::StackName

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: !Ref PublicSubnet1CIDR
MapPublicIpOnLaunch: true
Tags:
-
Key: Name
Value: !Sub AWS::StackName Public Subnet (AZ1)

PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [1, !GetAZs ""]
CidrBlock: !Ref PublicSubnet2CIDR
MapPublicIpOnLaunch: true
Tags:
-
Key: Name
Value: !Sub AWS::StackName Public Subnet (AZ2)

PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
-
Key: Name
Value: !Sub AWS::StackName Public Route Table

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

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

PublicSubnets:
Description: A list of the public subnets
Value: !Join [",", [!Ref PublicSubnet1, !Ref PublicSubnet2]]

PublicSubnet1:
Description: Public subnet 1 ID
Value: !Ref PublicSubnet1
Export:
Name:
!Sub "${AWS::StackName}-PublicSubnet1"

PublicSubnet2:
Description: Public subnet 2 ID
Value: !Ref PublicSubnet2
Export:
Name:
!Sub "${AWS::StackName}-PublicSubnet2"

Key Features:

  • Two availability zones for high availability
  • Public subnets with automatic public IP assignment
  • Internet Gateway for outbound connectivity
  • CloudFormation Exports for cross-stack references
  • Customizable CIDR blocks via parameters

Usage:

aws cloudformation create-stack \
--stack-name my-vpc \
--template-body file://vpc.yaml \
--parameters \
ParameterKey=VpcCIDR,ParameterValue=10.7.0.0/16 \
ParameterKey=PublicSubnet1CIDR,ParameterValue=10.7.1.0/24 \
ParameterKey=PublicSubnet2CIDR,ParameterValue=10.7.2.0/24

VPC IAM Policy

This policy demonstrates CloudFormation stack update controls using resource-level permissions.

File: Advanced CloudFormation Concepts/vpc-policy.json

{
"Statement" : [
{
"Effect" : "Allow",
"Action" : "Update:*",
"Principal": "*",
"Resource" : "*"
},
{
"Effect" : "Deny",
"Action" : "Update:Replace",
"Principal": "*",
"Resource" : "LogicalResourceId/VPC",
"Condition" : {
"StringLike" : {
"ResourceType" : ["AWS::EC2::VPC"]
}
}
}
]
}

Key Concepts:

  • Allows all update operations except replacement of the VPC resource
  • Prevents accidental deletion and recreation of the VPC
  • Uses resource-level permissions to enforce governance
  • Applied via StackPolicy in CloudFormation

2. Load Balancing & Auto Scaling

Application Load Balancer Template

Creates an ALB with listener, target group, and health checks configured.

File: Advanced CloudFormation Concepts/load-balancer.yaml

Description: >
AWS CloudFormation Template that creates an application load balancer,
listener, and target group. Requires network stack name as a parameter.

Parameters:
VPCStackName:
Description: >
Name of an active CloudFormation stack that contains the VPC
resources to be used by this stack.
Type: String
MinLength: 1
MaxLength: 255
AllowedPattern: "^[a-zA-Z][-a-zA-Z0-9]*$"

Resources:
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "Allow inbound HTTP from the load balancer"
SecurityGroupIngress:
-
IpProtocol: tcp
FromPort: '80'
ToPort: '80'
CidrIp: "0.0.0.0/0"
VpcId:
Fn::ImportValue:
Fn::Sub: "${VPCStackName}-VPC"

ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Subnets:
- Fn::ImportValue:
!Sub "${VPCStackName}-PublicSubnet1"
- Fn::ImportValue:
!Sub "${VPCStackName}-PublicSubnet2"
SecurityGroups:
- Ref: ALBSecurityGroup

ALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn:
Ref: ApplicationLoadBalancer
DefaultActions:
- Type: forward
TargetGroupArn:
!Ref ALBTargetGroup
Port: '80'
Protocol: HTTP

ALBTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
VpcId:
Fn::ImportValue:
!Sub "${VPCStackName}-VPC"
HealthCheckIntervalSeconds: 30
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
Port: 80
Protocol: HTTP
UnhealthyThresholdCount: 5
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: '20'
- Key: stickiness.enabled
Value: 'true'
- Key: stickiness.type
Value: lb_cookie
- Key: stickiness.lb_cookie.duration_seconds
Value: '30'

Outputs:
ALBTargetGroup:
Description: Target group ARN
Value: !Ref ALBTargetGroup
Export:
Name:
!Sub "${AWS::StackName}-TargetGroup"

WebsiteURL:
Description: URL for ALB
Value:
Fn::Join:
- ''
- - http://
- Fn::GetAtt:
- ApplicationLoadBalancer
- DNSName

Key Features:

  • Cross-stack references using Fn::ImportValue
  • Session stickiness (duration 30 seconds)
  • Graceful deregistration (20 second timeout)
  • Health checks every 30 seconds with 2 healthy/5 unhealthy thresholds
  • Exports ALB URL and target group ARN for use in other stacks

Auto Scaling Template

Creates a launch template and Auto Scaling Group with proper IAM roles for CloudWatch and SSM.

File: Advanced CloudFormation Concepts/auto-scaling.yaml

---
Description: >
AWS CloudFormation Template that creates a launch template and an Auto
Scaling Group. Requires network stack name as a parameter.

Parameters:
VPCStackName:
Description: >
Name of an active CloudFormation stack that contains the VPC
resources to be used by this stack.
Type: String
MinLength: 1
MaxLength: 255
AllowedPattern: "^[a-zA-Z][-a-zA-Z0-9]*$"

KeyName:
Description: Name of an existing EC2 key pair
Type: AWS::EC2::KeyPair::KeyName
ConstraintDescription: Must be the name of an existing EC2 key pair

GroupSize:
Default: '2'
Description: The initial nuber of Webserver instances
Type: Number

InstanceType:
Description: Webserver EC2 instance type
Type: String
Default: t2.micro
AllowedValues:
- t2.nano
- t2.micro
- t2.small
- t2.medium
- t2.large
ConstraintDescription: Must be a valid T2 EC2 instance type.

SSHLocation:
Description: "IP address range that can SSH to the EC2 instances"
Type: String
MinLength: '9'
MaxLength: '18'
Default: 0.0.0.0/0
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.

Mappings:
AWSRegion2AMI:
us-east-1:
HVM64: ami-97785bed
us-west-2:
HVM64: ami-f2d3638a
us-west-1:
HVM64: ami-824c4ee2
eu-west-1:
HVM64: ami-d834aba1
eu-west-2:
HVM64: ami-403e2524
eu-west-3:
HVM64: ami-8ee056f3
eu-central-1:
HVM64: ami-5652ce39
ap-northeast-1:
HVM64: ami-ceafcba8
ap-northeast-2:
HVM64: ami-863090e8
ap-northeast-3:
HVM64: ami-83444afe
ap-southeast-1:
HVM64: ami-68097514
ap-southeast-2:
HVM64: ami-942dd1f6
ap-south-1:
HVM64: ami-531a4c3c
us-east-2:
HVM64: ami-f63b1193
ca-central-1:
HVM64: ami-a954d1cd
sa-east-1:
HVM64: ami-84175ae8
cn-north-1:
HVM64: ami-cb19c4a6
cn-northwest-1:
HVM64: ami-3e60745c

Resources:
ALBStack:
Description: The nested stack containing the application load balancer.
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/architecting-operational-excellence-aws/load-balancer.yaml
Parameters:
VPCStackName: !Ref VPCStackName

InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "Allow inbound HTTP and SSH"
SecurityGroupIngress:
-
IpProtocol: tcp
FromPort: '80'
ToPort: '80'
CidrIp: "0.0.0.0/0"
-
IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp:
Ref: SSHLocation
VpcId:
Fn::ImportValue:
Fn::Sub: "${VPCStackName}-VPC"

InstanceRole:
Type: AWS::IAM::Role
Properties:
RoleName: InstanceRole
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM
- arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy

InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: InstanceProfile
Path: "/"
Roles:
- Ref: InstanceRole

WebserverLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateData:
ImageId:
Fn::FindInMap:
- AWSRegion2AMI
- !Ref AWS::Region
- HVM64
IamInstanceProfile:
Arn:
Fn::GetAtt:
- InstanceProfile
- Arn
InstanceType:
Ref: InstanceType
SecurityGroupIds:
- Ref: InstanceSecurityGroup
KeyName:
Ref: KeyName
UserData:
Fn::Base64:
Fn::Sub: |
#!/bin/bash -xe
sudo yum -y update
sudo yum -y install aws-cfn-bootstrap
/opt/aws/bin/cfn-signal -e 0 --region ${AWS::Region} --stack ${AWS::StackName} --resource AutoScalingGroup

AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
VPCZoneIdentifier:
- Fn::ImportValue:
Fn::Sub: "${VPCStackName}-PublicSubnet1"
- Fn::ImportValue:
Fn::Sub: "${VPCStackName}-PublicSubnet2"
LaunchTemplate:
LaunchTemplateId:
Ref: WebserverLaunchTemplate
Version: '1'
MinSize: '1'
MaxSize: '6'
DesiredCapacity:
Ref: GroupSize
TargetGroupARNs:
- Fn::GetAtt: [ ALBStack, Outputs.ALBTargetGroup ]
CreationPolicy:
ResourceSignal:
Timeout: PT10M
Count:
Ref: GroupSize

Key Features:

  • Regional AMI mappings for multi-region deployments
  • IAM instance profile with SSM and CloudWatch permissions
  • CloudFormation signal to wait for proper instance initialization
  • Nested stack integration with ALB
  • Min/Max/Desired capacity for scaling control

Load Balancer Update Template

Demonstrates updating ALB configuration without replacing resources.

File: CloudFormation/load-balancer-update.yaml

Description: >
AWS CloudFormation Template that creates an application load balancer,
listener, and target group. Requires network stack name as a parameter.

Parameters:
VPCStackName:
Description: >
Name of an active CloudFormation stack that contains the VPC
resources to be used by this stack.
Type: String
MinLength: 1
MaxLength: 255
AllowedPattern: "^[a-zA-Z][-a-zA-Z0-9]*$"

Resources:
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "Allow inbound HTTP from the load balancer"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '80'
ToPort: '80'
CidrIp: "0.0.0.0/0"
VpcId:
Fn::ImportValue:
Fn::Sub: "${VPCStackName}-VPC"

ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Subnets:
- Fn::ImportValue:
!Sub "${VPCStackName}-PublicSubnet1"
- Fn::ImportValue:
!Sub "${VPCStackName}-PublicSubnet2"

ALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn:
Ref: ApplicationLoadBalancer
DefaultActions:
- Type: forward
TargetGroupArn:
!Ref ALBTargetGroup
Port: '80'
Protocol: HTTP

ALBTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
VpcId:
Fn::ImportValue:
!Sub "${VPCStackName}-VPC"
HealthCheckIntervalSeconds: 30
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
Port: 80
Protocol: HTTP
UnhealthyThresholdCount: 5
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: '20'
- Key: stickiness.enabled
Value: 'true'
- Key: stickiness.type
Value: lb_cookie
- Key: stickiness.lb_cookie.duration_seconds
Value: '300'

Outputs:
ALBTargetGroup:
Description: Target group ARN
Value: !Ref ALBTargetGroup
Export:
Name:
!Sub "${AWS::StackName}-TargetGroup"

WebsiteURL:
Description: URL for ALB
Value:
Fn::Join:
- ''
- - http://
- Fn::GetAtt:
- ApplicationLoadBalancer
- DNSName

Changes from original:

  • Increased stickiness duration from 30 to 300 seconds
  • Removed security group from ALB properties (uses defaults)
  • Simplified configuration for update-only scenarios

3. CloudWatch & AWS Config

Systems Manager Automation for Patching

Executes patch baselines on new EC2 instances triggered by EventBridge.

File: CloudWatch Events and AWS Config/PatchNewInstances.yml

---
schemaVersion: '0.3'
description: Executes the AWS-RunPatchBaseline command document against an instance.
assumeRole: "arn:aws:iam::xxxxxxxxxxxx:role/SSM-PatchNewInstanceRole"
parameters:
InstanceId:
type: String
description: "(Required) ID of EC2 instance to patch"
mainSteps:
- name: sleep
action: aws:sleep
inputs:
Duration: PT4M
- name: scan
action: aws:runCommand
maxAttempts: 1
onFailure: Continue
inputs:
DocumentName: AWS-RunPatchBaseline
InstanceIds:
- "{{InstanceId}}"
Parameters:
Operation: Install

Key Features:

  • 4-minute delay before patching (allows SSM agent to start)
  • Calls AWS-managed patch baseline document
  • Install operation applies all available patches
  • Continues on failure to prevent stack rollback

Usage Scenario: When EventBridge detects an EC2 instance launch, this automation document is invoked to automatically patch the instance.


IAM Policy for SSM Operations

Grants permissions for Systems Manager to execute patch operations.

File: CloudWatch Events and AWS Config/InlinePolicyForSSM-PatchNewInstanceRole.txt

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ssm:SendCommand",
"ssm:ListCommands",
"ssm:DescribeInstanceInformation",
"ssm:ListCommandInvocations"
],
"Resource": "*"
}
]
}

Permissions:

  • ssm:SendCommand - Execute commands on instances
  • ssm:ListCommands - View command history
  • ssm:DescribeInstanceInformation - Query instance inventory
  • ssm:ListCommandInvocations - Check command results

IAM Policy for EventBridge

Allows EventBridge to invoke Systems Manager automation.

File: CloudWatch Events and AWS Config/InlinePolicyForEvents-PatchNewInstanceRole.txt

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "ssm:StartAutomationExecution",
"Resource": "*"
}
]
}

Purpose: EventBridge rule needs permission to execute the SSM automation document when new instances are detected.


EventBridge Input Transformer

Transforms EC2 instance launch events into automation document parameters.

File: CloudWatch Events and AWS Config/InputTransformer.txt

InputTemplate
"{"InstanceId":[<instance>]}"

InputPathsMap
"instance": "$.detail.EC2InstanceId"

How it works:

  1. EventBridge captures EC2 instance launch event
  2. Extracts EC2InstanceId from event detail
  3. Passes as parameter to SSM automation document
  4. Automation receives <instance> variable in JSON

4. AWS Developer Tools (CI/CD)

CodeDeploy Agent Installation

SSM document for installing CodeDeploy agent on Linux systems.

File: AWS Developer Tools - CodeCommit, CodeDeploy, and CodePipeline/installCodeDeployAgent.yaml

---
schemaVersion: '2.2'
description: cross-platform sample
mainSteps:
- action: aws:runShellScript
name: InstallCodeDeployAgent
precondition:
StringEquals:
- platformType
- Linux
inputs:
runCommand:
- yum install -y ruby
- yum install -y aws-cli
- cd /home/ec2-user
- aws s3 cp s3://aws-codedeploy-us-east-1/latest/install . --region us-east-1
- chmod +x ./install
- ./install auto

Installation steps:

  1. Install Ruby (CodeDeploy agent requirement)
  2. Install AWS CLI
  3. Download CodeDeploy installer from S3 bucket
  4. Make installer executable
  5. Run with auto flag (auto-starts and enables on boot)

Deploy with CloudFormation:

UserData:
Fn::Base64:
Fn::Sub: |
#!/bin/bash -xe
sudo yum -y update
sudo yum -y install aws-cfn-bootstrap
yum install -y ruby
yum install -y aws-cli
cd /home/ec2-user
aws s3 cp s3://aws-codedeploy-us-east-1/latest/install . --region us-east-1
chmod +x ./install
./install auto

Auto Scaling for CodeDeploy

Launch template and Auto Scaling Group with CodeDeploy agent pre-installed.

File: AWS Developer Tools - CodeCommit, CodeDeploy, and CodePipeline/auto-scaling-update.yaml

---
Description: >
AWS CloudFormation Template that creates a launch template and an Auto
Scaling Group. Requires network stack name as a parameter.

Parameters:
VPCStackName:
Description: >
Name of an active CloudFormation stack that contains the VPC
resources to be used by this stack.
Type: String
MinLength: 1
MaxLength: 255
AllowedPattern: "^[a-zA-Z][-a-zA-Z0-9]*$"

KeyName:
Description: Name of an existing EC2 key pair
Type: AWS::EC2::KeyPair::KeyName
ConstraintDescription: Must be the name of an existing EC2 key pair

GroupSize:
Default: '2'
Description: The initial nuber of Webserver instances
Type: Number

InstanceType:
Description: Webserver EC2 instance type
Type: String
Default: t2.micro
AllowedValues:
- t2.nano
- t2.micro
- t2.small
- t2.medium
- t2.large
ConstraintDescription: Must be a valid T2 EC2 instance type.

SSHLocation:
Description: "IP address range that can SSH to the EC2 instances"
Type: String
MinLength: '9'
MaxLength: '18'
Default: 0.0.0.0/0
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.

Mappings:
AWSRegion2AMI:
us-east-1:
HVM64: ami-97785bed
us-west-2:
HVM64: ami-f2d3638a
us-west-1:
HVM64: ami-824c4ee2
eu-west-1:
HVM64: ami-d834aba1
eu-west-2:
HVM64: ami-403e2524
eu-west-3:
HVM64: ami-8ee056f3
eu-central-1:
HVM64: ami-5652ce39
ap-northeast-1:
HVM64: ami-ceafcba8
ap-northeast-2:
HVM64: ami-863090e8
ap-northeast-3:
HVM64: ami-83444afe
ap-southeast-1:
HVM64: ami-68097514
ap-southeast-2:
HVM64: ami-942dd1f6
ap-south-1:
HVM64: ami-531a4c3c
us-east-2:
HVM64: ami-f63b1193
ca-central-1:
HVM64: ami-a954d1cd
sa-east-1:
HVM64: ami-84175ae8
cn-north-1:
HVM64: ami-cb19c4a6
cn-northwest-1:
HVM64: ami-3e60745c

Resources:
ALBStack:
Description: The nested stack containing the application load balancer.
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/architecting-operational-excellence-aws/load-balancer.yaml
Parameters:
VPCStackName: !Ref VPCStackName

InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "Allow inbound HTTP and SSH"
SecurityGroupIngress:
-
IpProtocol: tcp
FromPort: '80'
ToPort: '80'
CidrIp: "0.0.0.0/0"
-
IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp:
Ref: SSHLocation
VpcId:
Fn::ImportValue:
Fn::Sub: "${VPCStackName}-VPC"

InstanceRole:
Type: AWS::IAM::Role
Properties:
RoleName: InstanceRole
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM
- arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy

InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: InstanceProfile
Path: "/"
Roles:
- Ref: InstanceRole

WebserverLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateData:
ImageId:
Fn::FindInMap:
- AWSRegion2AMI
- !Ref AWS::Region
- HVM64
IamInstanceProfile:
Arn:
Fn::GetAtt:
- InstanceProfile
- Arn
InstanceType:
Ref: InstanceType
SecurityGroupIds:
- Ref: InstanceSecurityGroup
KeyName:
Ref: KeyName
UserData:
Fn::Base64:
Fn::Sub: |
#!/bin/bash -xe
sudo yum -y update
sudo yum -y install aws-cfn-bootstrap
yum install -y ruby
yum install -y aws-cli
cd /home/ec2-user
aws s3 cp s3://aws-codedeploy-us-east-1/latest/install . --region us-east-1
chmod +x ./install
./install auto
/opt/aws/bin/cfn-signal -e 0 --region ${AWS::Region} --stack ${AWS::StackName} --resource AutoScalingGroup

AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
VPCZoneIdentifier:
- Fn::ImportValue:
Fn::Sub: "${VPCStackName}-PublicSubnet1"
- Fn::ImportValue:
Fn::Sub: "${VPCStackName}-PublicSubnet2"
LaunchTemplate:
LaunchTemplateId:
Ref: WebserverLaunchTemplate
Version: '2'
MinSize: '1'
MaxSize: '6'
DesiredCapacity:
Ref: GroupSize
TargetGroupARNs:
- Fn::GetAtt: [ ALBStack, Outputs.ALBTargetGroup ]
CreationPolicy:
ResourceSignal:
Timeout: PT10M
Count:
Ref: GroupSize

Difference from generic Auto Scaling:

  • UserData includes CodeDeploy agent installation
  • All instances ready for immediate application deployment
  • No additional manual configuration needed

5. Sample Application

Application Specification (AppSpec)

Defines deployment steps for CodeDeploy.

File: SampleApp/appspec.yml

version: 0.0
os: linux
files:
- source: index.html
destination: /var/www/html
hooks:
BeforeInstall:
- location: scripts/install_dependencies.sh
timeout: 180
runas: root
AfterInstall:
- location: scripts/change_index.sh
timeout: 30
runas: root
- location: scripts/start_server.sh
timeout: 60
runas: root
ApplicationStop:
- location: scripts/stop_server.sh
timeout: 60
runas: root

Lifecycle:

  1. ApplicationStop: Stop existing web server
  2. BeforeInstall: Install Apache HTTP server
  3. File copy: Deploy index.html to web root
  4. AfterInstall: Customize page and start server

Install Dependencies

Installs Apache HTTP Server.

File: SampleApp/scripts/install_dependencies.sh

#!/bin/bash
yum install -y httpd

Start Server

Starts the web service.

File: SampleApp/scripts/start_server.sh

#!/bin/bash
service httpd start

Stop Server

Gracefully stops the web service.

File: SampleApp/scripts/stop_server.sh

#!/bin/bash
isExistApp = `pgrep httpd`
if [[ -n $isExistApp ]]; then
service httpd stop
fi

Change Index

Customizes the index page with deployment ID.

File: SampleApp/scripts/change_index.sh

#!/bin/bash
sed -i -e "s/DEPLOYMENT_ID/$DEPLOYMENT_ID/g" /var/www/html/index.html

This script replaces the placeholder with the actual CodeDeploy deployment ID.


Sample HTML Page

Displays deployment success confirmation.

File: SampleApp/index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sample Deployment</title>
<style>
body {
color: #ffffff;
background-color: #0188cc;
font-family: Arial, sans-serif;
font-size: 14px;
}

h1 {
font-size: 500%;
font-weight: normal;
margin-bottom: 0;
}

h2 {
font-size: 200%;
font-weight: normal;
margin-bottom: 0;
}
</style>
</head>
<body>
<div align="center">
<h1>Congratulations!</h1>
<h2>You deployed this application using AWS CodeDeploy.</h2>
<h3>Deployment ID: DEPLOYMENT_ID</h3>
<p><a href="https://benpiper.com">benpiper.com</a></p>
</div>
</body>
</html>

Deployment Workflow

Step 1: Create VPC Stack

aws cloudformation create-stack \
--stack-name prod-vpc \
--template-body file://vpc.yaml

Step 2: Create ALB Stack

aws cloudformation create-stack \
--stack-name prod-alb \
--template-body file://load-balancer.yaml \
--parameters ParameterKey=VPCStackName,ParameterValue=prod-vpc

Step 3: Create Auto Scaling Stack

aws cloudformation create-stack \
--stack-name prod-compute \
--template-body file://auto-scaling.yaml \
--parameters \
ParameterKey=VPCStackName,ParameterValue=prod-vpc \
ParameterKey=KeyName,ParameterValue=my-keypair \
ParameterKey=GroupSize,ParameterValue=2

Step 4: Deploy Application via CodeDeploy

# Push code to CodeCommit
git push origin main

# Trigger CodePipeline (automatically deploys to instances)
# Or manually create deployment:
aws deploy create-deployment \
--application-name MyApp \
--deployment-group-name production \
--s3-location s3://my-bucket/app.zip \
--deployment-config-name CodeDeployDefault.OneAtATime

Best Practices

  1. Use nested stacks for modular infrastructure (VPC, ALB, Compute)
  2. Export values across stacks using Fn::ImportValue and Export
  3. Implement stack policies to prevent accidental resource replacement
  4. Use mappings for region-specific values (AMIs, availability zones)
  5. Add IAM roles with least-privilege permissions to instances
  6. Use CloudFormation signals (cfn-signal) to wait for proper initialization
  7. Document parameters with descriptions and constraints
  8. Version launch templates when making updates
  9. Test updates with --change-set before applying
  10. Monitor deployments with CloudWatch and EventBridge automation