--- name: aws-cloudformation-ec2 description: Provides 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. category: aws tags: [aws, cloudformation, ec2, security-group, iam-role, alb, load-balancer, target-group, spot, infrastructure, iaac] version: 1.0.0 allowed-tools: Read, Write, Bash --- # AWS CloudFormation EC2 Infrastructure ## Overview 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. ## When to Use Use this skill when: - Creating new EC2 instances (On-Demand or SPOT) - Configuring Security Groups for network access control - Creating IAM roles and instance profiles for EC2 - Setting up Application Load Balancers (ALB) with target groups - Implementing template Parameters with AWS-specific types - Creating Outputs for cross-stack references - Organizing templates with Mappings and Conditions - Designing reusable, modular CloudFormation templates ## Instructions Follow these steps to create EC2 infrastructure with CloudFormation: 1. **Define Instance Parameters**: Specify instance type, AMI ID, and key pair 2. **Configure Security Groups**: Create rules for inbound/outbound traffic 3. **Set Up IAM Roles**: Define instance profile for AWS access 4. **Add Storage**: Configure EBS volumes and mount points 5. **Implement User Data**: Add bootstrap scripts for initialization 6. **Configure Monitoring**: Enable detailed monitoring if needed 7. **Add ALB Integration**: Set up load balancer and target groups 8. **Use Spot Instances**: Configure for cost optimization For complete examples, see the [EXAMPLES.md](references/examples.md) file. ## Examples The following examples demonstrate common EC2 patterns: ### Example 1: EC2 Instance with Security Group ```yaml EC2Instance: Type: AWS::EC2::Instance Properties: InstanceType: t3.micro ImageId: !Ref AmiId KeyName: !Ref KeyName SecurityGroupIds: - !Ref InstanceSecurityGroup IamInstanceProfile: !Ref InstanceProfile UserData: Fn::Base64: | #!/bin/bash yum update -y yum install -y httpd systemctl start httpd ``` ### Example 2: Security Group Configuration ```yaml InstanceSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Sub "${AWS::StackName}-sg" GroupDescription: Security group for EC2 instance VpcId: !Ref VpcId SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: 10.0.0.0/16 ``` ### Example 3: IAM Role for EC2 ```yaml IAMRole: Type: AWS::IAM::Role Properties: RoleName: !Sub "${AWS::StackName}-role" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: ec2.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: InstanceProfileName: !Sub "${AWS::StackName}-profile" Roles: - !Ref IAMRole ``` For complete production-ready examples, see [EXAMPLES.md](references/examples.md). ## Quick Start ### Basic EC2 Instance with Secure Configuration ```yaml AWSTemplateFormatVersion: 2010-09-09 Description: Simple EC2 instance with secure SSH access Parameters: LatestAmiId: Type: AWS::SSM::Parameter::Value 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.PublicIp ``` ### EC2 Instance with IAM Role ```yaml AWSTemplateFormatVersion: 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 properties ``` ## Template Structure ### Template Sections Overview AWS CloudFormation templates are JSON or YAML files with specific sections. Each section serves a purpose in defining your infrastructure. ```yaml 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 creation ``` ### Format Version The `AWSTemplateFormatVersion` identifies the template version. Current version is `2010-09-09`. ```yaml AWSTemplateFormatVersion: 2010-09-09 Description: My CloudFormation Template ``` ### Description Add a description to document the template's purpose. Must appear after the format version. ```yaml 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 permissions ``` ### Metadata Use `Metadata` for additional information about resources or parameters. ```yaml 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 Pair ``` ### Resources Section The `Resources` section is the only required section. It defines AWS resources to provision. ```yaml Resources: MyInstance: Type: AWS::EC2::Instance Properties: ImageId: ami-0ff8a95407f89df2f InstanceType: t3.micro KeyName: my-key-pair Tags: - Key: Name Value: !Sub ${AWS::StackName}-instance ``` ## Parameters ### Parameter Types Use AWS-specific parameter types for validation and easier selection in the console. ```yaml 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 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 ALB ``` ### SSM Parameter Types Reference Systems Manager parameters for dynamic values. ```yaml Parameters: LatestAmiId: Type: AWS::SSM::Parameter::Value 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 Description: Latest ARM AMI ID from SSM Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2 ``` ### Parameter Constraints Add constraints to validate parameter values. ```yaml 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: 1000 ``` ## Mappings Use `Mappings` for static configuration data based on regions or other factors. ```yaml 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] ``` ## Conditions Use `Conditions` to conditionally create resources based on parameters. ```yaml 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.0 ``` ## Transform Use `Transform` for macros like AWS::Serverless for SAM templates. ```yaml 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 and Cross-Stack References ### Basic Outputs ```yaml 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.AvailabilityZone ``` ### Exporting Values for Cross-Stack References Export values so other stacks can import them. ```yaml 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}-TargetGroupArn ``` ### Importing Values in Another Stack ```yaml Parameters: 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}-InstanceRoleArn ``` ### Cross-Stack Reference Pattern Create a dedicated security stack that exports values: ```yaml # 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}-InstanceRoleArn ``` Application stack imports these values: ```yaml # 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}-InstanceRoleArn ``` ## EC2 Instances ### Basic Instance Configuration ```yaml Resources: 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 EnvironmentName ``` ### Instance with Multiple Volumes ```yaml Resources: 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: st1 ``` ### Instance with Detailed Monitoring ```yaml Resources: Ec2Instance: Type: AWS::EC2::Instance Properties: ImageId: !Ref AmiId InstanceType: !Ref InstanceType Monitoring: true Metrics: CollectionInterval: 60 ``` ### Instance with Placement ```yaml Resources: Ec2Instance: Type: AWS::EC2::Instance Properties: ImageId: !Ref AmiId InstanceType: !Ref InstanceType Placement: AvailabilityZone: !Select [0, !GetAZs ''] GroupName: !Ref PlacementGroup Tenancy: default ``` ## SPOT Instances ### SPOT Fleet ```yaml AWSTemplateFormatVersion: 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: "*" ``` ### SPOT Instance Request ```yaml 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: persistent ``` ### SPOT Instance with Fallback ```yaml Resources: 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 SubnetId ``` ## Security Groups ### Basic Security Group ```yaml Resources: 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-sg ``` ### Security Group with Self Reference ```yaml Resources: 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-sg ``` ### Security Group for ALB ```yaml Resources: 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 AppSecurityGroup ``` ## IAM Roles ### EC2 IAM Role with Least Privilege ```yaml Resources: 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}-profile ``` ### Role for Systems Manager ```yaml Resources: 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}/*" ``` ## Application Load Balancer (ALB) ### Basic ALB ```yaml 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: HTTP ``` ### ALB with HTTPS ```yaml Resources: 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: HTTP ``` ### ALB with Multiple Target Groups ```yaml Resources: 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: 100 ``` ### ALB with Cross-Zone Load Balancing ```yaml Resources: 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: true ``` ## Complete Example ### Full EC2 Stack with ALB ```yaml AWSTemplateFormatVersion: 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 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 "

Hello from $(hostname)

" > /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.Arn ``` ## Best Practices ### Use AWS-Specific Parameter Types Always use AWS-specific parameter types for validation and easier selection. ```yaml Parameters: VpcId: Type: AWS::EC2::VPC::Id Description: Select a VPC SubnetIds: Type: List Description: Select subnets SecurityGroupIds: Type: List Description: Select security groups InstanceType: Type: AWS::EC2::InstanceType Description: EC2 instance type KeyName: Type: AWS::EC2::KeyPair::KeyName Description: Select a key pair ``` ### Organize by Lifecycle Separate resources that change at different rates into different stacks. ```yaml # 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::LoadBalancer ``` ### Use Meaningful Names Use `AWS::StackName` and parameters for consistent naming. ```yaml Resources: Ec2Instance: Type: AWS::EC2::Instance Properties: Tags: - Key: Name Value: !Sub ${AWS::StackName}-instance - Key: Environment Value: !Ref EnvironmentName ``` ### Use Pseudo Parameters Use pseudo parameters for region-agnostic templates. ```yaml 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 Before Deployment ```bash # 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.yaml ``` ### Stack Policies Stack policies protect stack resources from unintended updates that could cause service disruption. Apply policies to prevent accidental modifications or deletions of critical resources. ```yaml 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: Retain ``` **Stack Policy JSON Example:** ```json { "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:** ```bash 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.json ``` ### Termination Protection Enable termination protection to prevent accidental deletion of production stacks. This is critical for production environments. ```bash # 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:** - Enable on all production stacks - Use AWS Organizations SCP to enforce termination protection - Review before deleting development stacks - Document the process for emergency termination ### Drift Detection Drift detection identifies differences between the actual infrastructure and the CloudFormation template. Regular drift checks ensure compliance and security. ```bash # 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 10 ``` **Detect Drift Programmatically:** ```bash #!/bin/bash # detect-drift.sh - Automated drift detection script STACK_NAME=$1 if [ -z "$STACK_NAME" ]; then echo "Usage: $0 " 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" fi ``` **Common 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 Change sets preview the impact of stack changes before execution. Always review change sets in production environments. ```bash # 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-changeset ``` **Change 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:** ```bash # 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:** ```bash #!/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] " 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 ``` ## Related Resources - For advanced examples: See [EXAMPLES.md](EXAMPLES.md) - For reference: See [REFERENCE.md](REFERENCE.md) - AWS CloudFormation User Guide: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/ - AWS EC2 Documentation: https://docs.aws.amazon.com/ec2/ - AWS ALB Documentation: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/ ## Constraints and Warnings ### Resource Limits - **Instance Limits**: Maximum number of EC2 instances per region varies by account - **ENI Limits**: Each instance type has maximum ENI limits - **Security Groups**: Maximum 500 security groups per VPC - **EBS Volumes**: Maximum number of EBS volumes per account is 20 by default ### Instance Constraints - **Instance Type Availability**: Not all instance types available in all regions/AZs - **Instance Replacement**: Changing instance type requires instance replacement - **Tenancy Changes**: Changing between default and dedicated tenancy requires replacement - **Host Resource Group**: Dedicated Hosts have specific allocation and utilization constraints ### Storage Constraints - **EBS Volume Limits**: EBS volume size varies by volume type (gp3 up to 16 TB) - **IOPS Limits**: Provisioned IOPS (io1/io2) has specific IOPS per GB limits - **Snapshot Costs**: EBS snapshots incur storage costs even after source volume deletion - **Throughput**: Volume throughput limits vary by volume type and size ### Network Constraints - **ENI Limits**: Each instance type has maximum ENI and IP address per ENI limits - **Public IP Assignment**: Public IP addresses are released on instance stop - **Elastic IP Limits**: Each account has limited number of Elastic IPs - **Security Group Rules**: Maximum 60 inbound and 60 outbound rules per security group ### Security Constraints - **Key Pairs**: Key pairs cannot be recovered if lost; instance access lost - **IAM Roles**: Instance profile roles cannot be changed after instance creation - **Password Authentication**: Password authentication for EC2 is disabled by default; use key pairs - **Metadata Service**: IMDSv2 should be enabled; IMDSv1 has security vulnerabilities ### Cost Considerations - **Instance Costs**: Instances incur costs while running; stopped instances may still incur EBS costs - **EBS Costs**: EBS volumes incur costs per GB-month even if not attached - **Data Transfer**: Data transfer out to internet incurs significant costs - **Load Balancer Costs**: ALBs/ELBs/NLBs incur hourly and per-GB data processing costs - **Elastic IP**: Unattached Elastic IPs incur small hourly costs ### Availability Constraints - **Spot Instances**: Spot instances can be terminated with 2-minute notice - **Reserved Instances**: Reserved instances require commitment and have specific term limits - **Availability Zones**: Not all instance types available in all AZs - **Capacity Limits**: On-Demand capacity limits may prevent launching instances during high demand ## Additional Files