tessl install github:giuseppe-trisciuoglio/developer-kit --skill aws-cloudformation-ec2github.com/giuseppe-trisciuoglio/developer-kit
AWS CloudFormation patterns for EC2 instances, Security Groups, IAM roles, and load balancers. Use when creating EC2 instances, SPOT instances, Security Groups, IAM roles for EC2, Application Load Balancers (ALB), Target Groups, and implementing template structure with Parameters, Outputs, Mappings, Conditions, and cross-stack references.
Review Score
79%
Validation Score
10/16
Implementation Score
65%
Activation Score
100%
Create production-ready EC2 infrastructure using AWS CloudFormation templates. This skill covers EC2 instances (On-Demand and SPOT), Security Groups, IAM roles and instance profiles, Application Load Balancers (ALB), Target Groups, template structure best practices, parameter patterns, and cross-stack references for modular, reusable infrastructure as code.
Use this skill when:
AWSTemplateFormatVersion: 2010-09-09
Description: Simple EC2 instance with secure SSH access
Parameters:
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
InstanceType:
Type: String
Default: t3.micro
AllowedValues:
- t3.micro
- t3.small
- t3.medium
KeyName:
Type: AWS::EC2::KeyPair::KeyName
Description: SSH key pair name
SshLocation:
Type: String
Description: CIDR block for SSH access
Default: 10.0.0.0/16
AllowedPattern: ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$
Resources:
InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for EC2 instance
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref SshLocation
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Ec2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: !Ref InstanceType
KeyName: !Ref KeyName
SecurityGroupIds:
- !Ref InstanceSecurityGroup
SubnetId: !Ref SubnetId
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-instance
Outputs:
InstanceId:
Description: EC2 Instance ID
Value: !Ref Ec2Instance
PublicIp:
Description: Public IP address
Value: !GetAtt Ec2Instance.PublicIpAWSTemplateFormatVersion: 2010-09-09
Description: EC2 instance with IAM role for S3 access
Resources:
# IAM Role for EC2 with least privilege permissions
Ec2Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns: []
Policies:
- PolicyName: S3WriteAccess
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
Resource: !Sub "arn:aws:s3:::${S3BucketName}/*"
# Instance Profile
Ec2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref Ec2Role
InstanceProfileName: !Sub ${AWS::StackName}-profile
# EC2 Instance
Ec2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: !Ref InstanceType
IamInstanceProfile: !Ref Ec2InstanceProfile
# ... other propertiesAWS CloudFormation templates are JSON or YAML files with specific sections. Each section serves a purpose in defining your infrastructure.
AWSTemplateFormatVersion: 2010-09-09 # Required - template version
Description: Optional description string # Optional description
# Section order matters for readability but CloudFormation accepts any order
Mappings: {} # Static configuration tables
Metadata: {} # Additional information about resources
Parameters: {} # Input values for customization
Rules: {} # Parameter validation rules
Conditions: {} # Conditional resource creation
Transform: {} # Macro processing (e.g., AWS::Serverless)
Resources: {} # AWS resources to create (REQUIRED)
Outputs: {} # Return values after stack creationThe AWSTemplateFormatVersion identifies the template version. Current version is 2010-09-09.
AWSTemplateFormatVersion: 2010-09-09
Description: My CloudFormation TemplateAdd a description to document the template's purpose. Must appear after the format version.
AWSTemplateFormatVersion: 2010-09-09
Description: >
This template creates an EC2 instance with a security group
and IAM role for running web applications. It includes:
- EC2 instance configuration
- Security group with HTTP/HTTPS access
- IAM role with S3 access permissionsUse Metadata for additional information about resources or parameters.
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: EC2 Configuration
Parameters:
- InstanceType
- KeyName
- Label:
default: Network Configuration
Parameters:
- VpcId
- SubnetId
ParameterLabels:
InstanceType:
default: EC2 Instance Type
KeyName:
default: SSH Key PairThe Resources section is the only required section. It defines AWS resources to provision.
Resources:
MyInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0ff8a95407f89df2f
InstanceType: t3.micro
KeyName: my-key-pair
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-instanceUse AWS-specific parameter types for validation and easier selection in the console.
Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
Description: Select an existing VPC
SubnetId:
Type: AWS::EC2::Subnet::Id
Description: Select a subnet
SecurityGroupIds:
Type: List<AWS::EC2::SecurityGroup::Id>
Description: Select existing security groups
InstanceType:
Type: AWS::EC2::InstanceType
Description: EC2 instance type
Default: t3.micro
AllowedValues:
- t3.micro
- t3.small
- t3.medium
- t3.large
AmiId:
Type: AWS::EC2::Image::Id
Description: Select an AMI
KeyName:
Type: AWS::EC2::KeyPair::KeyName
Description: Select an existing key pair
AlbArn:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer::Arn
Description: Select an ALBReference Systems Manager parameters for dynamic values.
Parameters:
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Description: Latest AMI ID from SSM
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
LatestAmiIdARM:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Description: Latest ARM AMI ID from SSM
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2Add constraints to validate parameter values.
Parameters:
VpcCidr:
Type: String
Description: CIDR block for the VPC
Default: 10.0.0.0/16
AllowedPattern: ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$
ConstraintDescription: Must be a valid CIDR block (x.x.x.x/x)
InstanceCount:
Type: Number
Description: Number of instances to launch
Default: 1
MinValue: 1
MaxValue: 10
Environment:
Type: String
Description: Deployment environment
Default: development
AllowedValues:
- development
- staging
- production
ConstraintDescription: Must be development, staging, or production
VolumeSize:
Type: Number
Description: EBS volume size in GB
Default: 20
MinValue: 8
MaxValue: 1000Use Mappings for static configuration data based on regions or other factors.
Mappings:
RegionMap:
us-east-1:
HVM64: ami-0ff8a95407f89df2f
HVMG2: ami-0a0c776d80e2a1f3c
us-west-2:
HVM64: ami-0a0c776d80e2a1f3c
HVMG2: ami-0a0c776d80e2a1f3c
eu-west-1:
HVM64: ami-0ff8a95407f89df2f
HVMG2: ami-0a0c776d80e2a1f3c
EnvironmentConfig:
development:
InstanceType: t3.micro
MinInstances: 1
MaxInstances: 2
EnableAutoScaling: false
staging:
InstanceType: t3.small
MinInstances: 1
MaxInstances: 3
EnableAutoScaling: false
production:
InstanceType: t3.medium
MinInstances: 2
MaxInstances: 10
EnableAutoScaling: true
Resources:
Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !FindInMap [RegionMap, !Ref AWS::Region, HVM64]
InstanceType: !FindInMap [EnvironmentConfig, !Ref Environment, InstanceType]Use Conditions to conditionally create resources based on parameters.
Parameters:
DeployAlb:
Type: String
Default: true
AllowedValues:
- true
- false
Environment:
Type: String
Default: development
AllowedValues:
- development
- staging
- production
UseSpotInstance:
Type: String
Default: false
AllowedValues:
- true
- false
Conditions:
ShouldDeployAlb: !Equals [!Ref DeployAlb, true]
IsProduction: !Equals [!Ref Environment, production]
ShouldUseSpot: !Equals [!Ref UseSpotInstance, true]
IsNotDevelopment: !Not [!Equals [!Ref Environment, development]]
Resources:
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Condition: ShouldDeployAlb
Properties:
Scheme: internet-facing
SecurityGroups:
- !Ref AlbSecurityGroup
Subnets: !Ref PublicSubnetIds
ProductionScalingPolicy:
Type: AWS::AutoScaling::ScalingPolicy
Condition: IsProduction
Properties:
AutoScalingGroupName: !Ref AutoScalingGroup
PolicyType: TargetTrackingScaling
TargetTrackingConfiguration:
PredefinedMetricSpecification:
PredefinedMetricType: ASGAverageCPUUtilization
TargetValue: 70.0Use Transform for macros like AWS::Serverless for SAM templates.
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: SAM template for serverless application with EC2
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: nodejs18.x
CodeUri: function/Outputs:
InstanceId:
Description: EC2 Instance ID
Value: !Ref Ec2Instance
PublicIp:
Description: Public IP address
Value: !GetAtt Ec2Instance.PublicIp
PrivateIp:
Description: Private IP address
Value: !GetAtt Ec2Instance.PrivateIp
AvailabilityZone:
Description: Availability Zone
Value: !GetAtt Ec2Instance.AvailabilityZoneExport values so other stacks can import them.
Outputs:
InstanceId:
Description: EC2 Instance ID for other stacks
Value: !Ref Ec2Instance
Export:
Name: !Sub ${AWS::StackName}-InstanceId
SecurityGroupId:
Description: Security Group ID for other stacks
Value: !Ref InstanceSecurityGroup
Export:
Name: !Sub ${AWS::StackName}-SecurityGroupId
InstanceRoleArn:
Description: IAM Role ARN for other stacks
Value: !GetAtt Ec2Role.Arn
Export:
Name: !Sub ${AWS::StackName}-InstanceRoleArn
TargetGroupArn:
Description: Target Group ARN for other stacks
Value: !Ref ApplicationTargetGroup
Export:
Name: !Sub ${AWS::StackName}-TargetGroupArnParameters:
VpcId:
Type: AWS::EC2::VPC::Id
Description: VPC ID from network stack
SecurityGroupId:
Type: AWS::EC2::SecurityGroup::Id
Description: Security Group ID from security stack
InstanceRoleArn:
Type: String
Description: IAM Role ARN from security stack
# Or use Fn::ImportValue for programmatic access
Resources:
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !ImportValue
Fn::Sub: ${NetworkStackName}-VpcId
GroupDescription: Security group for application
Instance:
Type: AWS::EC2::Instance
Properties:
IamInstanceProfile: !ImportValue
Fn::Sub: ${SecurityStackName}-InstanceRoleArnCreate a dedicated security stack that exports values:
# security-stack.yaml
AWSTemplateFormatVersion: 2010-09-09
Description: Security resources stack
Resources:
InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for EC2 instances
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Ec2Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
Outputs:
SecurityGroupId:
Value: !Ref InstanceSecurityGroup
Export:
Name: !Sub ${AWS::StackName}-SecurityGroupId
InstanceRoleArn:
Value: !GetAtt Ec2Role.Arn
Export:
Name: !Sub ${AWS::StackName}-InstanceRoleArnApplication stack imports these values:
# application-stack.yaml
AWSTemplateFormatVersion: 2010-09-09
Description: Application stack that imports from security stack
Parameters:
SecurityStackName:
Type: String
Description: Name of the security stack
Default: security-stack
Resources:
Ec2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref AmiId
InstanceType: !Ref InstanceType
SecurityGroupIds:
- !ImportValue
Fn::Sub: ${SecurityStackName}-SecurityGroupId
IamInstanceProfile: !ImportValue
Fn::Sub: ${SecurityStackName}-InstanceRoleArnResources:
Ec2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref AmiId
InstanceType: !Ref InstanceType
KeyName: !Ref KeyName
SubnetId: !Ref SubnetId
SecurityGroupIds:
- !Ref SecurityGroup
UserData:
Fn::Base64: |
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-instance
- Key: Environment
Value: !Ref EnvironmentNameResources:
Ec2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref AmiId
InstanceType: !Ref InstanceType
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
VolumeSize: 20
DeleteOnTermination: true
VolumeType: gp3
- DeviceName: /dev/xvdh
Ebs:
VolumeSize: 50
DeleteOnTermination: false
VolumeType: gp3
- DeviceName: /dev/xvdi
Ebs:
VolumeSize: 100
DeleteOnTermination: false
VolumeType: st1Resources:
Ec2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref AmiId
InstanceType: !Ref InstanceType
Monitoring: true
Metrics:
CollectionInterval: 60Resources:
Ec2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref AmiId
InstanceType: !Ref InstanceType
Placement:
AvailabilityZone: !Select [0, !GetAZs '']
GroupName: !Ref PlacementGroup
Tenancy: defaultAWSTemplateFormatVersion: 2010-09-09
Description: SPOT Fleet for cost-optimized instances
Parameters:
MaxPrice:
Type: Number
Default: 0.05
Description: Maximum price per instance hour
Resources:
SpotFleet:
Type: AWS::EC2::SpotFleet
Properties:
SpotFleetRequestConfigData:
TargetCapacity: 10
IamFleetRole: !GetAtt SpotFleetRole.Arn
LaunchSpecifications:
- InstanceType: t3.micro
ImageId: !Ref AmiId
SubnetId: !Ref SubnetId
WeightedCapacity: 1
- InstanceType: t3.small
ImageId: !Ref AmiId
SubnetId: !Ref SubnetId
WeightedCapacity: 2
AllocationStrategy: lowestPrice
SpotPrice: !Sub ${MaxPrice}
SpotFleetRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: spotfleet.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: SpotFleetPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- ec2:DescribeInstances
- ec2:DescribeImages
Resource: "*"Resources:
SpotRequest:
Type: AWS::EC2::SpotFleet
Properties:
SpotFleetRequestConfigData:
TargetCapacity: 1
IamFleetRole: !GetAtt SpotFleetRole.Arn
LaunchSpecifications:
- InstanceType: t3.medium
ImageId: !Ref AmiId
SubnetId: !Ref SubnetId
KeyName: !Ref KeyName
Type: persistentResources:
OnDemandInstance:
Type: AWS::EC2::Instance
Condition: IsNotSpot
Properties:
ImageId: !Ref AmiId
InstanceType: !Ref InstanceType
SpotInstance:
Type: AWS::EC2::SpotFleet
Condition: UseSpot
Properties:
SpotFleetRequestConfigData:
TargetCapacity: 1
IamFleetRole: !GetAtt SpotFleetRole.Arn
LaunchSpecifications:
- InstanceType: t3.medium
ImageId: !Ref AmiId
SubnetId: !Ref SubnetIdResources:
WebSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for web servers
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 10.0.0.0/16
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-web-sgResources:
AppSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for application
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-app-sg
DatabaseSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for database
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !Ref AppSecurityGroup
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-db-sgResources:
AlbSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for Application Load Balancer
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref AppSecurityGroup
- IpProtocol: tcp
FromPort: 443
ToPort: 443
SourceSecurityGroupId: !Ref AppSecurityGroupResources:
Ec2Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns: []
Policies:
- PolicyName: S3Access
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:DeleteObject
Resource: !Sub "arn:aws:s3:::${S3BucketName}/*"
- PolicyName: CloudWatchLogs
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- logs:DescribeLogStreams
Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/ec2/${EnvironmentName}/*"
Ec2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref Ec2Role
InstanceProfileName: !Sub ${AWS::StackName}-profileResources:
SsmRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
Policies:
- PolicyName: S3ReadAccess
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:GetObject
Resource: !Sub "arn:aws:s3:::${S3BucketName}/*"Resources:
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub ${AWS::StackName}-alb
Scheme: internet-facing
SecurityGroups:
- !Ref AlbSecurityGroup
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
Type: application
ApplicationTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub ${AWS::StackName}-tg
Port: 80
Protocol: HTTP
VpcId: !Ref VpcId
TargetType: instance
HealthCheckPath: /health
HealthCheckIntervalSeconds: 30
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
UnhealthyThresholdCount: 3
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-tg
ApplicationListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref ApplicationTargetGroup
LoadBalancerArn: !Ref ApplicationLoadBalancer
Port: 80
Protocol: HTTPResources:
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub ${AWS::StackName}-alb
Scheme: internet-facing
SecurityGroups:
- !Ref AlbSecurityGroup
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
ApplicationTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub ${AWS::StackName}-tg
Port: 443
Protocol: HTTPS
VpcId: !Ref VpcId
TargetType: instance
HealthCheckPath: /health
HealthCheckProtocol: HTTPS
ApplicationListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref ApplicationTargetGroup
LoadBalancerArn: !Ref ApplicationLoadBalancer
Port: 443
Protocol: HTTPS
Certificates:
- CertificateArn: !Ref CertificateArn
ApplicationListenerHttpRedirect:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: redirect
RedirectConfig:
Host: "#{host}"
Path: "/#{path}"
Port: "443"
Protocol: "HTTPS"
Query: "#{query}"
StatusCode: HTTP_301
LoadBalancerArn: !Ref ApplicationLoadBalancer
Port: 80
Protocol: HTTPResources:
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub ${AWS::StackName}-alb
Scheme: internet-facing
SecurityGroups:
- !Ref AlbSecurityGroup
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
ApiTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub ${AWS::StackName}-api-tg
Port: 8080
Protocol: HTTP
VpcId: !Ref VpcId
HealthCheckPath: /actuator/health
WebTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub ${AWS::StackName}-web-tg
Port: 80
Protocol: HTTP
VpcId: !Ref VpcId
HealthCheckPath: /health
ApiListenerRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
Actions:
- Type: forward
TargetGroupArn: !Ref ApiTargetGroup
Conditions:
- Field: path-pattern
Values:
- /api/*
- /v1/*
ListenerArn: !Ref ApplicationListener
Priority: 10
WebListenerRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
Actions:
- Type: forward
TargetGroupArn: !Ref WebTargetGroup
Conditions:
- Field: path-pattern
Values:
- /*
ListenerArn: !Ref ApplicationListener
Priority: 100Resources:
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub ${AWS::StackName}-alb
Scheme: internet-facing
SecurityGroups:
- !Ref AlbSecurityGroup
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
LoadBalancerAttributes:
- Key: load_balancing.cross_zone.enabled
Value: trueAWSTemplateFormatVersion: 2010-09-09
Description: Complete EC2 stack with ALB, security groups, and IAM role
Parameters:
EnvironmentName:
Type: String
Default: production
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
InstanceType:
Type: String
Default: t3.micro
AllowedValues:
- t3.micro
- t3.small
- t3.medium
- t3.large
VpcCidr:
Type: String
Default: 10.0.0.0/16
Resources:
# VPC
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-vpc
- Key: Environment
Value: !Ref EnvironmentName
# Internet Gateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-igw
- Key: Environment
Value: !Ref EnvironmentName
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
# Public Subnets
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-public-1
- Key: SubnetType
Value: Public
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: !Select [1, !GetAZs '']
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-public-2
- Key: SubnetType
Value: Public
# Security Group for ALB
AlbSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for ALB
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-alb-sg
- Key: Environment
Value: !Ref EnvironmentName
# Security Group for EC2
InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for EC2 instances
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref AlbSecurityGroup
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 10.0.0.0/16
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-instance-sg
- Key: Environment
Value: !Ref EnvironmentName
# IAM Role with specific permissions
Ec2Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
Policies:
- PolicyName: S3Access
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
Resource: !Sub "arn:aws:s3:::${S3BucketName}/*"
- PolicyName: CloudWatchLogs
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/ec2/${EnvironmentName}/*"
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-ec2-role
Ec2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref Ec2Role
# Application Load Balancer
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub ${EnvironmentName}-alb
Scheme: internet-facing
SecurityGroups:
- !Ref AlbSecurityGroup
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
Type: application
ApplicationTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub ${EnvironmentName}-tg
Port: 80
Protocol: HTTP
VpcId: !Ref VPC
TargetType: instance
HealthCheckPath: /health
HealthCheckIntervalSeconds: 30
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
UnhealthyThresholdCount: 3
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-tg
ApplicationListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref ApplicationTargetGroup
LoadBalancerArn: !Ref ApplicationLoadBalancer
Port: 80
Protocol: HTTP
# EC2 Instance
Ec2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: !Ref InstanceType
IamInstanceProfile: !Ref Ec2InstanceProfile
SecurityGroupIds:
- !Ref InstanceSecurityGroup
SubnetId: !Ref PublicSubnet1
UserData:
Fn::Base64: |
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from $(hostname)</h1>" > /var/www/html/index.html
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-instance
- Key: Environment
Value: !Ref EnvironmentName
Outputs:
InstanceId:
Description: EC2 Instance ID
Value: !Ref Ec2Instance
InstancePublicIp:
Description: EC2 Instance Public IP
Value: !GetAtt Ec2Instance.PublicIp
LoadBalancerDnsName:
Description: ALB DNS Name
Value: !GetAtt ApplicationLoadBalancer.DNSName
TargetGroupArn:
Description: Target Group ARN
Value: !Ref ApplicationTargetGroup
SecurityGroupId:
Description: Instance Security Group ID
Value: !Ref InstanceSecurityGroup
RoleArn:
Description: IAM Role ARN
Value: !GetAtt Ec2Role.ArnAlways use AWS-specific parameter types for validation and easier selection.
Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
Description: Select a VPC
SubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: Select subnets
SecurityGroupIds:
Type: List<AWS::EC2::SecurityGroup::Id>
Description: Select security groups
InstanceType:
Type: AWS::EC2::InstanceType
Description: EC2 instance type
KeyName:
Type: AWS::EC2::KeyPair::KeyName
Description: Select a key pairSeparate resources that change at different rates into different stacks.
# Network stack - rarely changes
AWSTemplateFormatVersion: 2010-09-09
Description: Network infrastructure (VPC, subnets, routes)
Resources:
VPC: AWS::EC2::VPC
Subnets: AWS::EC2::Subnet
# Security stack - changes occasionally
AWSTemplateFormatVersion: 2010-09-09
Description: Security resources (IAM roles, security groups)
Resources:
SecurityGroups: AWS::EC2::SecurityGroup
Roles: AWS::IAM::Role
# Application stack - changes frequently
AWSTemplateFormatVersion: 2010-09-09
Description: Application resources (EC2 instances, ALB)
Parameters:
NetworkStackName:
Type: String
SecurityStackName:
Type: String
Resources:
Instances: AWS::EC2::Instance
LoadBalancer: AWS::ElasticLoadBalancingV2::LoadBalancerUse AWS::StackName and parameters for consistent naming.
Resources:
Ec2Instance:
Type: AWS::EC2::Instance
Properties:
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-instance
- Key: Environment
Value: !Ref EnvironmentNameUse pseudo parameters for region-agnostic templates.
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${AWS::StackName}-${AWS::AccountId}-${AWS::Region}
Ec2Instance:
Type: AWS::EC2::Instance
Properties:
Tags:
- Key: StackName
Value: !Ref AWS::StackName
- Key: Region
Value: !Ref AWS::Region# Validate template
aws cloudformation validate-template --template-body file://template.yaml
# Check for syntax errors
aws cloudformation validate-template \
--template-body file://template.yaml \
--query 'Description'
# Use cfn-lint for advanced validation
pip install cfn-lint
cfn-lint template.yamlStack policies protect stack resources from unintended updates that could cause service disruption. Apply policies to prevent accidental modifications or deletions of critical resources.
Resources:
# EC2 instance - allow updates but prevent deletion
Ec2Instance:
Type: AWS::EC2::Instance
DeletionPolicy: Retain
UpdateReplacePolicy: Retain
# Database - prevent all updates
DatabaseInstance:
Type: AWS::RDS::DBInstance
DeletionPolicy: Snapshot
UpdateReplacePolicy: RetainStack Policy JSON Example:
{
"Statement": [
{
"Effect": "Allow",
"Action": "Update:*",
"Principal": "*",
"Resource": "*"
},
{
"Effect": "Deny",
"Action": ["Update:Delete", "Update:Replace"],
"Principal": "*",
"Resource": "LogicalId=Ec2Instance"
},
{
"Effect": "Deny",
"Action": "Update:*",
"Principal": "*",
"Resource": "LogicalId=DatabaseInstance"
}
]
}Apply Stack Policy:
aws cloudformation set-stack-policy \
--stack-name my-ec2-stack \
--stack-policy-body file://stack-policy.json
# Or from a file
aws cloudformation set-stack-policy \
--stack-name my-ec2-stack \
--stack-policy-url https://s3.amazonaws.com/bucket/policy.jsonEnable termination protection to prevent accidental deletion of production stacks. This is critical for production environments.
# Enable termination protection when creating a stack
aws cloudformation create-stack \
--stack-name my-ec2-stack \
--template-body file://template.yaml \
--enable-termination-protection
# Enable termination protection on existing stack
aws cloudformation update-termination-protection \
--stack-name my-ec2-stack \
--enable-termination-protection
# Disable termination protection (use with caution)
aws cloudformation update-termination-protection \
--stack-name my-ec2-stack \
--no-enable-termination-protection
# Check if termination protection is enabled
aws cloudformation describe-stacks \
--stack-name my-ec2-stack \
--query 'Stacks[0].EnableTerminationProtection'Best Practices for Termination Protection:
Drift detection identifies differences between the actual infrastructure and the CloudFormation template. Regular drift checks ensure compliance and security.
# Detect drift on a stack
aws cloudformation detect-drift \
--stack-name my-ec2-stack
# Get drift detection status
aws cloudformation describe-stack-drift-detection-status \
--stack-drift-detection-id abc123
# Get resources that have drifted
aws cloudformation describe-stack-resource-drifts \
--stack-name my-ec2-stack
# Get detailed drift information for a specific resource
aws cloudformation describe-stack-resource-drifts \
--stack-name my-ec2-stack \
--stack-resource-drifts-limit 10Detect Drift Programmatically:
#!/bin/bash
# detect-drift.sh - Automated drift detection script
STACK_NAME=$1
if [ -z "$STACK_NAME" ]; then
echo "Usage: $0 <stack-name>"
exit 1
fi
echo "Detecting drift for stack: $STACK_NAME"
# Start drift detection
DETECTION_ID=$(aws cloudformation detect-drift \
--stack-name $STACK_NAME \
--query 'StackId' \
--output text)
echo "Drift detection started: $DETECTION_ID"
# Wait for drift detection to complete
STATUS="DETECTION_IN_PROGRESS"
while [ "$STATUS" = "DETECTION_IN_PROGRESS" ]; do
sleep 5
STATUS=$(aws cloudformation describe-stack-drift-detection-status \
--stack-drift-detection-id $DETECTION_ID \
--query 'DetectionStatus' \
--output text)
echo "Status: $STATUS"
done
# Get drift status
DRIFT_STATUS=$(aws cloudformation describe-stack-drift-detection-status \
--stack-drift-detection-id $DETECTION_ID \
--query 'DriftStatus' \
--output text)
echo "Drift Status: $DRIFT_STATUS"
if [ "$DRIFT_STATUS" = "DRIFTED" ]; then
echo "Resources with drift:"
aws cloudformation describe-stack-resource-drifts \
--stack-name $STACK_NAME \
--query 'StackResourceDrifts[].{LogicalId:LogicalResourceId,Status:ResourceDriftStatus,Type:ResourceType}'
else
echo "No drift detected - stack is in sync with template"
fiCommon Drift Scenarios:
| Drift Type | Description | Action Required |
|---|---|---|
| MODIFIED | Resource properties changed | Review and update template or revert changes |
| DELETED | Resource deleted outside CFN | Recreate via template or import |
| ADDED | Resource created outside CFN | Import to stack or delete manually |
Change sets preview the impact of stack changes before execution. Always review change sets in production environments.
# Create a change set
aws cloudformation create-change-set \
--stack-name my-ec2-stack \
--change-set-name my-ec2-changeset \
--template-body file://updated-template.yaml \
--capabilities CAPABILITY_IAM \
--change-set-type UPDATE
# List change sets for a stack
aws cloudformation list-change-sets \
--stack-name my-ec2-stack
# Describe a change set to see the planned changes
aws cloudformation describe-change-set \
--stack-name my-ec2-stack \
--change-set-name my-ec2-changeset
# Execute a change set
aws cloudformation execute-change-set \
--stack-name my-ec2-stack \
--change-set-name my-ec2-changeset
# Delete a change set if changes are not needed
aws cloudformation delete-change-set \
--stack-name my-ec2-stack \
--change-set-name my-ec2-changesetChange Set Types:
| Type | Description | Use Case |
|---|---|---|
| UPDATE | Preview changes to existing stack | Modifying existing resources |
| CREATE | Preview new stack creation | Creating new stacks from template |
| IMPORT | Preview resources to import | Importing existing resources |
Review Change Sets with Filters:
# Get changes affecting specific resource types
aws cloudformation describe-change-set \
--stack-name my-ec2-stack \
--change-set-name my-ec2-changeset \
--query 'Changes[?ResourceChange.ResourceType==`AWS::EC2::Instance`]'
# Get changes with replacement impact
aws cloudformation describe-change-set \
--stack-name my-ec2-stack \
--change-set-name my-ec2-changeset \
--query 'Changes[?ResourceChange.Replacement!=`None`]'Automated Change Set Review Script:
#!/bin/bash
# review-changeset.sh - Automated change set review
STACK_NAME=$1
CHANGE_SET_NAME=$2
AUTO_APPROVE=false
while getopts "a" opt; do
case $opt in
a) AUTO_APPROVE=true ;;
*) echo "Usage: $0 [-a] <stack-name> <change-set-name>"
echo " -a: Auto-approve if no critical changes"
exit 1 ;;
esac
done
echo "Reviewing change set: $CHANGE_SET_NAME"
echo "Stack: $STACK_NAME"
echo ""
# Get change set summary
CHANGES=$(aws cloudformation describe-change-set \
--stack-name $STACK_NAME \
--change-set-name $CHANGE_SET_NAME \
--query 'Changes[*].{Type:Type,Resource:ResourceChange.LogicalResourceId,Action:ResourceChange.Action,Replacement:ResourceChange.Replacement}' \
--output table)
echo "Planned Changes:"
echo "$CHANGES"
echo ""
# Check for critical changes (replacements or deletions)
CRITICAL_CHANGES=$(aws cloudformation describe-change-set \
--stack-name $STACK_NAME \
--change-set-name $CHANGE_SET_NAME \
--query 'Changes[?ResourceChange.Replacement==`True` || ResourceChange.Action==`Remove`]' \
--output json)
if [ -n "$CRITICAL_CHANGES" ] && [ "$CRITICAL_CHANGES" != "[]" ]; then
echo "WARNING: Critical changes detected that require manual review:"
echo "$CRITICAL_CHANGES"
echo ""
echo "Please review manually before executing."
exit 1
fi
if [ "$AUTO_APPROVE" = true ]; then
echo "No critical changes - auto-executing change set..."
aws cloudformation execute-change-set \
--stack-name $STACK_NAME \
--change-set-name $CHANGE_SET_NAME
echo "Change set executed successfully."
else
echo "Review complete. No critical changes detected."
echo "To execute: aws cloudformation execute-change-set --stack-name $STACK_NAME --change-set-name $CHANGE_SET_NAME"
fi