Complete Guide to AWS CloudFormation: Infrastructure as Code Made Simple
AWS CloudFormation is a service that helps you model and set up your AWS resources so that you can spend less time managing those resources and more time focusing on your applications. You create a template that describes all the AWS resources you want, and CloudFormation takes care of provisioning and configuring those resources for you.
Overview
CloudFormation enables Infrastructure as Code (IaC), allowing you to define your cloud infrastructure in JSON or YAML templates. This provides version control, repeatability, and consistency across environments.
Key Benefits
1. Infrastructure as Code
- Version control your infrastructure
- Track changes over time
- Peer review infrastructure changes
- Rollback to previous versions
2. Consistency and Repeatability
- Deploy identical environments
- Reduce human error
- Standardize deployments
- Ensure compliance
3. Automation and Efficiency
- Automate resource provisioning
- Integrate with CI/CD pipelines
- Reduce manual intervention
- Scale infrastructure quickly
4. Cost Management
- Delete entire stacks to clean up resources
- Estimate costs before deployment
- Tag resources for cost allocation
- Implement cost controls
Core Concepts
1. Templates
CloudFormation templates are JSON or YAML files that define your infrastructure:
AWSTemplateFormatVersion: '2010-09-09'Description: 'Simple EC2 instance template'
Parameters: InstanceType: Type: String Default: t3.micro Description: EC2 instance type
Resources: MyEC2Instance: Type: AWS::EC2::Instance Properties: InstanceType: !Ref InstanceType ImageId: ami-0c02fb55956c7d316 KeyName: my-key-pair
Outputs: InstanceId: Description: Instance ID Value: !Ref MyEC2Instance2. Stacks
A stack is a collection of AWS resources that you can manage as a single unit:
# Create stackaws cloudformation create-stack \ --stack-name my-web-app \ --template-body file://template.yaml \ --parameters ParameterKey=InstanceType,ParameterValue=t3.small
# Update stackaws cloudformation update-stack \ --stack-name my-web-app \ --template-body file://updated-template.yaml
# Delete stackaws cloudformation delete-stack --stack-name my-web-app3. Change Sets
Preview changes before applying them:
# Create change setaws cloudformation create-change-set \ --stack-name my-web-app \ --template-body file://new-template.yaml \ --change-set-name my-changeset
# Describe changesaws cloudformation describe-change-set \ --stack-name my-web-app \ --change-set-name my-changeset
# Execute change setaws cloudformation execute-change-set \ --stack-name my-web-app \ --change-set-name my-changesetTemplate Structure
1. Complete Template Format
AWSTemplateFormatVersion: '2010-09-09'Description: 'Template description'
Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: "Network Configuration" Parameters: - VpcCIDR - PublicSubnetCIDR
Parameters: VpcCIDR: Type: String Default: 10.0.0.0/16 Description: CIDR block for VPC
Mappings: RegionMap: us-east-1: AMI: ami-0c02fb55956c7d316 us-west-2: AMI: ami-0892d3c7ee96c0bf7
Conditions: CreateProdResources: !Equals [!Ref Environment, production]
Resources: MyVPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCIDR EnableDnsHostnames: true EnableDnsSupport: true
Outputs: VPCId: Description: VPC ID Value: !Ref MyVPC Export: Name: !Sub "${AWS::StackName}-VPC-ID"2. Intrinsic Functions
# Reference parameters and resourcesInstanceType: !Ref InstanceTypeParameter
# Join stringsUserData: !Base64 !Sub | #!/bin/bash echo "Hello from ${AWS::StackName}"
# Get attribute from resourceWebsiteURL: !GetAtt LoadBalancer.DNSName
# Conditional valuesInstanceType: !If [CreateProdResources, m5.large, t3.micro]
# Find in mapImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI]
# Split and selectAvailabilityZone: !Select [0, !GetAZs ""]Common Resource Types
1. VPC and Networking
Resources: VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 EnableDnsHostnames: true
PublicSubnet: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.0.1.0/24 AvailabilityZone: !Select [0, !GetAZs ""]
InternetGateway: Type: AWS::EC2::InternetGateway
AttachGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway
SecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Web server security group VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/02. EC2 Instances
Resources: WebServer: Type: AWS::EC2::Instance Properties: InstanceType: !Ref InstanceType ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI] KeyName: !Ref KeyPairName SubnetId: !Ref PublicSubnet SecurityGroupIds: - !Ref WebServerSecurityGroup UserData: !Base64 !Sub | #!/bin/bash yum update -y yum install -y httpd systemctl start httpd systemctl enable httpd echo "<h1>Hello from CloudFormation</h1>" > /var/www/html/index.html
ElasticIP: Type: AWS::EC2::EIP Properties: InstanceId: !Ref WebServer Domain: vpc3. Auto Scaling
Resources: LaunchTemplate: Type: AWS::EC2::LaunchTemplate Properties: LaunchTemplateName: !Sub "${AWS::StackName}-launch-template" LaunchTemplateData: ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI] InstanceType: !Ref InstanceType SecurityGroupIds: - !Ref WebServerSecurityGroup UserData: !Base64 !Sub | #!/bin/bash # Install and configure application
AutoScalingGroup: Type: AWS::AutoScaling::AutoScalingGroup Properties: VPCZoneIdentifier: - !Ref PublicSubnet1 - !Ref PublicSubnet2 LaunchTemplate: LaunchTemplateId: !Ref LaunchTemplate Version: !GetAtt LaunchTemplate.LatestVersionNumber MinSize: 1 MaxSize: 3 DesiredCapacity: 2 TargetGroupARNs: - !Ref TargetGroup4. RDS Database
Resources: DBSubnetGroup: Type: AWS::RDS::DBSubnetGroup Properties: DBSubnetGroupDescription: Subnet group for RDS database SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2
Database: Type: AWS::RDS::DBInstance Properties: DBInstanceIdentifier: !Sub "${AWS::StackName}-database" DBInstanceClass: db.t3.micro Engine: mysql EngineVersion: '8.0' MasterUsername: admin MasterUserPassword: !Ref DBPassword AllocatedStorage: 20 DBSubnetGroupName: !Ref DBSubnetGroup VPCSecurityGroups: - !Ref DatabaseSecurityGroupAdvanced Features
1. Nested Stacks
# Parent templateResources: NetworkStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: https://s3.amazonaws.com/my-bucket/network.yaml Parameters: VpcCIDR: 10.0.0.0/16
ApplicationStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: https://s3.amazonaws.com/my-bucket/application.yaml Parameters: VPCId: !GetAtt NetworkStack.Outputs.VPCId2. Stack Sets
Deploy stacks across multiple accounts and regions:
# Create stack setaws cloudformation create-stack-set \ --stack-set-name my-cross-account-stack \ --template-body file://template.yaml \ --capabilities CAPABILITY_NAMED_IAM
# Deploy to accounts and regionsaws cloudformation create-stack-instances \ --stack-set-name my-cross-account-stack \ --accounts 123456789012 234567890123 \ --regions us-east-1 us-west-23. Custom Resources
Resources: CustomResource: Type: AWS::CloudFormation::CustomResource Properties: ServiceToken: !GetAtt CustomResourceLambda.Arn CustomProperty: CustomValue
CustomResourceLambda: Type: AWS::Lambda::Function Properties: Handler: index.handler Role: !GetAtt LambdaExecutionRole.Arn Code: ZipFile: | import json import boto3 import urllib3
def handler(event, context): # Handle create, update, delete operations response_url = event['ResponseURL'] # Process the request and send response return send_response(response_url, event, context, "SUCCESS", {}) Runtime: python3.9Best Practices
1. Template Organization
# Use clear naming conventionsResources: WebServerInstance: Type: AWS::EC2::Instance # Clear, descriptive names
WebServerSecurityGroup: Type: AWS::EC2::SecurityGroup # Consistent naming pattern
# Use parameters for flexibilityParameters: Environment: Type: String AllowedValues: [dev, staging, prod]
InstanceType: Type: String Default: t3.micro AllowedValues: [t3.micro, t3.small, t3.medium]2. Security Best Practices
# Use least privilege IAM rolesWebServerRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: ec2.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
# Encrypt sensitive dataDatabase: Type: AWS::RDS::DBInstance Properties: StorageEncrypted: true KmsKeyId: !Ref DatabaseKMSKey
# Use Secrets Manager for passwordsDatabaseSecret: Type: AWS::SecretsManager::Secret Properties: GenerateSecretString: SecretStringTemplate: '{"username": "admin"}' GenerateStringKey: password PasswordLength: 16 ExcludeCharacters: '"@/\'3. Error Handling
# Use rollback configurationResources: AutoScalingGroup: Type: AWS::AutoScaling::AutoScalingGroup CreationPolicy: ResourceSignal: Count: 2 Timeout: PT10M UpdatePolicy: AutoScalingRollingUpdate: MinInstancesInService: 1 MaxBatchSize: 1 WaitOnResourceSignals: true PauseTime: PT10M
# Use DeletionPolicy for critical resourcesDatabase: Type: AWS::RDS::DBInstance DeletionPolicy: Snapshot Properties: # Database propertiesDeployment Strategies
1. Blue/Green Deployment
Parameters: BlueGreenDeployment: Type: String Default: blue AllowedValues: [blue, green]
Conditions: IsBlueDeployment: !Equals [!Ref BlueGreenDeployment, blue]
Resources: BlueTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Condition: IsBlueDeployment # Blue environment configuration
GreenTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Condition: !Not [!Condition IsBlueDeployment] # Green environment configuration2. Multi-Environment Templates
Mappings: EnvironmentMap: dev: InstanceType: t3.micro MinSize: 1 MaxSize: 2 prod: InstanceType: t3.medium MinSize: 2 MaxSize: 6
Resources: AutoScalingGroup: Type: AWS::AutoScaling::AutoScalingGroup Properties: MinSize: !FindInMap [EnvironmentMap, !Ref Environment, MinSize] MaxSize: !FindInMap [EnvironmentMap, !Ref Environment, MaxSize]Monitoring and Troubleshooting
1. CloudFormation Events
# Monitor stack eventsaws cloudformation describe-stack-events --stack-name my-stack
# Get stack statusaws cloudformation describe-stacks --stack-name my-stack2. Stack Drift Detection
# Detect driftaws cloudformation detect-stack-drift --stack-name my-stack
# Get drift resultsaws cloudformation describe-stack-drift-detection-status \ --stack-drift-detection-id drift-id3. Troubleshooting Failed Deployments
# Add detailed logging to user dataUserData: !Base64 !Sub | #!/bin/bash exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1 echo "Starting user data execution"
# Your commands here yum update -y
# Signal success/failure /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} \ --resource AutoScalingGroup --region ${AWS::Region}Integration with CI/CD
1. GitHub Actions
name: Deploy CloudFormation Stackon: push: branches: [main]
jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1
- name: Deploy CloudFormation Stack run: | aws cloudformation deploy \ --template-file template.yaml \ --stack-name my-application \ --parameter-overrides Environment=prod \ --capabilities CAPABILITY_IAM2. AWS CodePipeline
Resources: CodePipeline: Type: AWS::CodePipeline::Pipeline Properties: RoleArn: !GetAtt CodePipelineRole.Arn Stages: - Name: Source Actions: - Name: SourceAction ActionTypeId: Category: Source Owner: AWS Provider: S3 Version: 1 - Name: Deploy Actions: - Name: CreateChangeSet ActionTypeId: Category: Deploy Owner: AWS Provider: CloudFormation Version: 1 Configuration: ActionMode: CHANGE_SET_REPLACE StackName: my-stack ChangeSetName: my-changeset TemplatePath: SourceOutput::template.yamlCost Optimization
1. Resource Tagging
Resources: EC2Instance: Type: AWS::EC2::Instance Properties: Tags: - Key: Environment Value: !Ref Environment - Key: Project Value: !Ref ProjectName - Key: CostCenter Value: Engineering2. Lifecycle Policies
# S3 bucket with lifecycle policyS3Bucket: Type: AWS::S3::Bucket Properties: LifecycleConfiguration: Rules: - Id: DeleteOldVersions Status: Enabled NoncurrentVersionExpirationInDays: 30 - Id: TransitionToIA Status: Enabled TransitionInDays: 30 StorageClass: STANDARD_IA