AWS CloudFormation: Infrastructure as Code for Amazon Web Services

Automating and managing AWS infrastructure through declarative templates

Introduction to AWS CloudFormation

AWS CloudFormation is Amazon's native Infrastructure as Code (IaC) service that enables you to model, provision, and manage AWS resources by writing declarative templates. Instead of manually creating and configuring resources through the AWS Management Console or using multiple SDK calls, CloudFormation allows you to describe your entire infrastructure in a text file, which you can version, manage, and deploy just like application code.

CloudFormation was one of the first IaC services offered by major cloud providers, launching in 2011, and has since evolved into a sophisticated service that supports hundreds of AWS resource types across virtually all AWS services. It's deeply integrated with the AWS ecosystem, offering capabilities specifically designed for AWS resources that generic IaC tools may not provide.

graph TD A[CloudFormation Workflow] --> B[Author Template] B --> C[Create Stack] C --> D[CloudFormation Service] D --> E[Provision Resources] E --> F[Stack Created] F --> G[Stack Updates/Deletions] G --> D

The Recipe and Restaurant Kitchen Analogy

CloudFormation can be compared to a restaurant kitchen with standardized recipes:

  • Templates are like detailed recipes that specify all ingredients and steps needed to create a dish.
  • Stacks are like the actual dishes created from the recipes, where each dish is a collection of ingredients (resources) working together.
  • Parameters are like customer preferences that modify aspects of the recipe (e.g., "no onions" or "extra spicy").
  • Resource Dependencies are like cooking steps that must happen in a specific order (you can't add the sauce before cooking the pasta).
  • Stack Updates are like modifying a dish after it's been served—CloudFormation figures out the minimal changes needed, just as a chef would make targeted adjustments rather than remaking the entire dish.

Just as a restaurant with standardized recipes ensures consistent quality across many dishes, CloudFormation ensures consistent infrastructure deployment across multiple environments.

CloudFormation vs. Terraform

While both CloudFormation and Terraform are popular IaC tools, they have distinct characteristics that may make one more suitable than the other for particular use cases.

Feature AWS CloudFormation Terraform
Cloud Provider Support AWS only Multi-cloud (AWS, Azure, GCP, etc.)
AWS Integration Deep, native integration with all AWS services Good integration via AWS provider, but may lag behind new AWS features
Language JSON or YAML HashiCorp Configuration Language (HCL)
State Management Built-in (managed by AWS) External (managed by the user)
Resource Drift Detection Built-in Requires plan operation
Change Preview Change sets Plan operation
Rollback Automatic rollback on failure Manual or custom automation required
Ecosystem AWS-focused with CloudFormation Registry and AWS CDK Broader with provider plugins and module registry

When to Choose CloudFormation

CloudFormation might be the better choice when:

  • Your infrastructure is entirely within AWS
  • You want native integration with AWS services and IAM permissions
  • You need automatic rollback capabilities
  • You prefer not to manage state files
  • You want to use AWS-specific capabilities like nested stacks or stack sets
  • Your team already has AWS expertise and wants to minimize the learning curve

Terraform might be the better choice when:

  • You need to manage resources across multiple cloud providers
  • You prefer HCL's more concise syntax compared to JSON/YAML
  • You want more flexibility in module composition
  • You need more granular control over resource creation order
  • You want to leverage the broader ecosystem of Terraform providers

Key CloudFormation Concepts

Templates

Templates are text files in JSON or YAML format that define the resources and their properties that CloudFormation will create. They are the blueprints for building CloudFormation stacks.

Basic Template Structure


AWSTemplateFormatVersion: '2010-09-09'
Description: 'A simple EC2 instance template'

Parameters:
  InstanceType:
    Description: EC2 instance type
    Type: String
    Default: t2.micro
    AllowedValues: [t2.micro, t2.small, t2.medium]

Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      ImageId: ami-0c55b159cbfafe1f0
      Tags:
        - Key: Name
          Value: MyInstance

Outputs:
  InstanceId:
    Description: ID of the created EC2 instance
    Value: !Ref MyEC2Instance

Stacks

A stack is a collection of AWS resources that you can manage as a single unit. All resources in a stack are defined by the stack's CloudFormation template.

graph TD A[Stack] --> B[Resource 1: EC2 Instance] A --> C[Resource 2: Security Group] A --> D[Resource 3: S3 Bucket] A --> E[Resource 4: RDS Database] B --> F[Properties: Instance Type, AMI, etc.] C --> G[Properties: Ingress/Egress Rules, etc.] D --> H[Properties: Bucket Name, ACL, etc.] E --> I[Properties: Engine, Size, Credentials, etc.]

Resources

Resources are the AWS components that CloudFormation creates and manages, such as EC2 instances, S3 buckets, or IAM roles.

Resource Definition Examples


Resources:
  # EC2 Instance
  WebServer:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      ImageId: ami-0c55b159cbfafe1f0
      SecurityGroups:
        - !Ref WebServerSecurityGroup
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash -xe
          yum update -y
          yum install -y httpd
          systemctl start httpd
          systemctl enable httpd
          echo "

Hello from CloudFormation

" > /var/www/html/index.html # Security Group WebServerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow HTTP and SSH SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: 0.0.0.0/0 # S3 Bucket DataBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub "${AWS::StackName}-data-bucket" AccessControl: Private VersioningConfiguration: Status: Enabled

Parameters

Parameters enable you to input custom values to your template each time you create or update a stack.

Parameter Types and Constraints


Parameters:
  # String parameter with allowed values
  Environment:
    Description: The deployment environment
    Type: String
    Default: Development
    AllowedValues:
      - Development
      - Testing
      - Production
    
  # Number parameter with constraints
  WebServerCapacity:
    Description: The initial capacity of the web server Auto Scaling group
    Type: Number
    Default: 2
    MinValue: 1
    MaxValue: 10
    
  # AWS-specific parameter type
  VpcId:
    Description: VPC where resources will be created
    Type: AWS::EC2::VPC::Id
    
  # Parameter with regular expression constraint
  DBUsername:
    Description: Database admin username
    Type: String
    MinLength: 1
    MaxLength: 16
    AllowedPattern: "[a-zA-Z][a-zA-Z0-9]*"
    ConstraintDescription: must begin with a letter and contain only alphanumeric characters
    
  # Parameter group
  Parameters:
    ParameterGroups:
      - Label:
          default: "Network Configuration"
        Parameters:
          - VpcId
          - SubnetIds
      - Label:
          default: "Database Configuration"
        Parameters:
          - DBUsername
          - DBPassword

Intrinsic Functions

CloudFormation provides several built-in functions that help you manage and reference your resources and their properties.

Commonly Used Intrinsic Functions


Resources:
  MyBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Join ["-", [!Ref AWS::StackName, "my-bucket", !Ref "AWS::Region"]]
  
  MyInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceTypeParameter
      ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI]
      
  MyPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub "${AWS::StackName}-policy"
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action: 's3:GetObject'
            Resource: !GetAtt MyBucket.Arn
            
  MySecondInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      ImageId: ami-0c55b159cbfafe1f0
      SecurityGroupIds:
        - !If [CreateNewSecurityGroup, !Ref MySecurityGroup, !Ref ExistingSecurityGroupId]

Common Intrinsic Functions Reference

Function Description Example
!Ref References a parameter or resource !Ref MyParameter
!GetAtt Gets an attribute from a resource !GetAtt MyInstance.PrivateIp
!Join Joins strings with a delimiter !Join ["-", ["stack", "resource"]]
!Sub Substitutes variables in a string !Sub "Hello ${Name}"
!FindInMap Returns a named value from a mapping !FindInMap [RegionMap, !Ref "AWS::Region", AMI]
!If Returns one value if the condition is true and another if false !If [CreateProdResources, m5.large, t2.micro]
!Split Splits a string into a list !Split [",", "a,b,c"]
!Select Selects an item from a list !Select [0, !Split [",", "a,b,c"]]

Outputs

Outputs declare values that you want to make available outside of the stack, either for display in the AWS Console or for importing into other stacks.

Output Examples


Outputs:
  WebsiteURL:
    Description: URL for the website
    Value: !Sub "http://${WebServer.PublicDnsName}"
    
  BucketARN:
    Description: ARN of the created S3 bucket
    Value: !GetAtt DataBucket.Arn
    Export:
      Name: !Sub "${AWS::StackName}-BucketARN"
      
  VPCId:
    Description: ID of the VPC
    Value: !Ref VPC
    Export:
      Name: !Sub "${AWS::StackName}:VPCId"

Working with CloudFormation

Creating and Managing Stacks

CloudFormation stacks can be created and managed through the AWS Management Console, AWS CLI, or AWS SDKs.

AWS CLI Commands for Stack Management


# Create a new stack
aws cloudformation create-stack \
  --stack-name MyNetwork \
  --template-body file://network.yaml \
  --parameters ParameterKey=VpcCidr,ParameterValue=10.0.0.0/16

# Update an existing stack
aws cloudformation update-stack \
  --stack-name MyNetwork \
  --template-body file://network-updated.yaml \
  --parameters ParameterKey=VpcCidr,ParameterValue=10.0.0.0/16

# Delete a stack
aws cloudformation delete-stack \
  --stack-name MyNetwork

# Create a change set (preview changes)
aws cloudformation create-change-set \
  --stack-name MyNetwork \
  --change-set-name NetworkChanges \
  --template-body file://network-updated.yaml \
  --parameters ParameterKey=VpcCidr,ParameterValue=10.0.0.0/16

# Execute a change set
aws cloudformation execute-change-set \
  --stack-name MyNetwork \
  --change-set-name NetworkChanges

# List stacks
aws cloudformation list-stacks \
  --stack-status-filter CREATE_COMPLETE UPDATE_COMPLETE

# Describe a stack's resources
aws cloudformation describe-stack-resources \
  --stack-name MyNetwork

# Get stack outputs
aws cloudformation describe-stacks \
  --stack-name MyNetwork \
  --query "Stacks[0].Outputs"

Change Sets

Change sets allow you to preview how proposed changes to a stack might impact your running resources before implementing those changes.

sequenceDiagram participant U as User participant CF as CloudFormation participant AWS as AWS Resources U->>CF: Create Change Set CF->>CF: Compare Proposed State with Current State CF->>U: Display Planned Changes U->>CF: Review Change Set U->>CF: Execute Change Set CF->>AWS: Implement Changes AWS->>CF: Report Status CF->>U: Return Results

Change Set Best Practices

  • Always Preview: Use change sets for any update to production stacks
  • Review Replacements: Pay special attention to resources being replaced (potential data loss)
  • Check Dependencies: Understand how changes cascade to dependent resources
  • Consider Timing: Plan for interruptions from resource updates
  • Maintain History: Name change sets descriptively to maintain history
  • Test in Non-Production: Apply similar changes to test environments first

Stack Updates and Drift Detection

CloudFormation can update existing stacks and detect when resources have been modified outside of CloudFormation.

Stack Update Behaviors

Update Type Description Impact
No Interruption The update doesn't cause any interruption to the resource Changes like tags or certain configuration updates
Some Interruption The resource remains in place but may have some interruption Changes like instance type that require restart
Replacement CloudFormation creates a new resource and deletes the old one Changes to immutable properties like AMI ID

Drift Detection Commands


# Detect drift for the entire stack
aws cloudformation detect-stack-drift \
  --stack-name MyNetwork

# Get drift detection results
aws cloudformation describe-stack-drift-detection-status \
  --stack-drift-detection-id 8043a11c-5895-4a34-9bef-6b1deed33ddf

# Get information about drifted resources
aws cloudformation describe-stack-resource-drifts \
  --stack-name MyNetwork

CloudFormation Template Deep Dive

Template Sections

A complete CloudFormation template can include several optional sections:


AWSTemplateFormatVersion: '2010-09-09'
Description: 'A comprehensive CloudFormation template example'

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Network Configuration"
        Parameters:
          - VpcId
          - SubnetIds
      - Label:
          default: "Instance Configuration"
        Parameters:
          - InstanceType
          - KeyName

Parameters:
  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: VPC to deploy into

  SubnetIds:
    Type: List
    Description: Subnets to deploy into

  InstanceType:
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - t2.small
      - t2.medium
    Description: EC2 instance type

  KeyName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: SSH key pair for instance access

Mappings:
  RegionMap:
    us-east-1:
      AMI: ami-0b5eea76982371e91
    us-west-2:
      AMI: ami-0c5204531f799e0c6
    eu-west-1:
      AMI: ami-0664a710233d7c148

Conditions:
  IsProduction: !Equals [ !Ref Environment, "Production" ]
  CreateBackup: !And [ !Equals [ !Ref Environment, "Production" ], !Equals [ !Ref EnableBackup, "true" ] ]

Resources:
  MyInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      KeyName: !Ref KeyName
      ImageId: !FindInMap [ RegionMap, !Ref "AWS::Region", AMI ]
      SubnetId: !Select [ 0, !Ref SubnetIds ]
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-instance"
        - Key: Environment
          Value: !Ref Environment

  BackupBucket:
    Type: AWS::S3::Bucket
    Condition: CreateBackup
    Properties:
      BucketName: !Sub "${AWS::StackName}-backups"
      VersioningConfiguration:
        Status: Enabled

Outputs:
  InstanceId:
    Description: ID of the created instance
    Value: !Ref MyInstance

  InstancePrivateIp:
    Description: Private IP of the created instance
    Value: !GetAtt MyInstance.PrivateIp
    Export:
      Name: !Sub "${AWS::StackName}:InstancePrivateIp"

  BackupBucketName:
    Description: Name of the backup bucket
    Value: !If [ CreateBackup, !Ref BackupBucket, "No backup bucket created" ]

Resource Types and Properties

CloudFormation supports hundreds of AWS resource types, each with its own set of properties.

Common Resource Types

Resource Type Description Key Properties
AWS::EC2::Instance EC2 virtual machine InstanceType, ImageId, SecurityGroups
AWS::S3::Bucket S3 storage bucket BucketName, AccessControl, VersioningConfiguration
AWS::RDS::DBInstance Relational database Engine, DBInstanceClass, AllocatedStorage
AWS::EC2::VPC Virtual private cloud CidrBlock, EnableDnsSupport, Tags
AWS::IAM::Role IAM role for service permissions AssumeRolePolicyDocument, ManagedPolicyArns
AWS::Lambda::Function Lambda function Code, Handler, Runtime, Role
AWS::DynamoDB::Table DynamoDB table AttributeDefinitions, KeySchema, BillingMode

Mappings and Conditions

Mappings and conditions add flexibility to templates by allowing for conditional resource creation and environment-specific values.

Advanced Mappings Example


Mappings:
  EnvironmentMap:
    Development:
      InstanceType: t2.micro
      MultiAZ: false
      BackupRetention: 1
    Staging:
      InstanceType: t2.medium
      MultiAZ: false
      BackupRetention: 7
    Production:
      InstanceType: m5.large
      MultiAZ: true
      BackupRetention: 30
      
  RegionAMIMap:
    us-east-1:
      Amazon2: ami-0b5eea76982371e91
      Ubuntu20: ami-0758470213bdd23b1
    us-west-2:
      Amazon2: ami-0c5204531f799e0c6
      Ubuntu20: ami-0892d3c7ee96c0bf7
      
Resources:
  MyInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !FindInMap [EnvironmentMap, !Ref Environment, InstanceType]
      ImageId: !FindInMap [RegionAMIMap, !Ref "AWS::Region", !Ref OSType]

Advanced Conditions Example


Parameters:
  Environment:
    Type: String
    Default: Development
    AllowedValues: [Development, Staging, Production]
  
  MultiAZEnabled:
    Type: String
    Default: false
    AllowedValues: [true, false]
    
  AlertingEnabled:
    Type: String
    Default: false
    AllowedValues: [true, false]
    
Conditions:
  IsProduction: !Equals [!Ref Environment, Production]
  IsDevOrStaging: !Or [!Equals [!Ref Environment, Development], !Equals [!Ref Environment, Staging]]
  CreateAlarms: !Or [!Equals [!Ref AlertingEnabled, true], !Equals [!Ref Environment, Production]]
  CreateMultiAZ: !Or [!Equals [!Ref MultiAZEnabled, true], !Equals [!Ref Environment, Production]]
  CreateBackups: !Not [!Equals [!Ref Environment, Development]]
  
Resources:
  MyDatabase:
    Type: AWS::RDS::DBInstance
    Properties:
      Engine: mysql
      MultiAZ: !If [CreateMultiAZ, true, false]
      BackupRetentionPeriod: !If [CreateBackups, !If [IsProduction, 30, 7], 1]
      
  CPUAlarm:
    Type: AWS::CloudWatch::Alarm
    Condition: CreateAlarms
    Properties:
      AlarmDescription: High CPU utilization
      MetricName: CPUUtilization
      Namespace: AWS/RDS
      Statistic: Average
      Period: 300
      EvaluationPeriods: 2
      Threshold: !If [IsProduction, 70, 80]
      ComparisonOperator: GreaterThanThreshold
      Dimensions:
        - Name: DBInstanceIdentifier
          Value: !Ref MyDatabase

Advanced CloudFormation Features

Nested Stacks

Nested stacks allow you to compose CloudFormation templates, referencing one stack resource from another.

Parent Template with Nested Stack


Resources:
  NetworkStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://s3.amazonaws.com/bucket/network.yaml
      Parameters:
        VpcCidr: 10.0.0.0/16
        
  ApplicationStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://s3.amazonaws.com/bucket/application.yaml
      Parameters:
        VpcId: !GetAtt NetworkStack.Outputs.VpcId
        SubnetIds: !GetAtt NetworkStack.Outputs.SubnetIds
        
Outputs:
  ApplicationUrl:
    Description: URL for the application
    Value: !GetAtt ApplicationStack.Outputs.LoadBalancerUrl

Nested Stack Benefits

  • Reusability: Create common infrastructure patterns as reusable components
  • Modularity: Break down complex infrastructure into manageable pieces
  • Simplified Management: Update components independently while maintaining relationships
  • Resource Limits: Work around the 500-resource limit for a single stack
  • Organization: Group related resources together logically

Stack Sets

StackSets enable you to deploy stacks across multiple AWS accounts and regions with a single operation.

graph TD A[Stack Set] --> B[Stack Instance: Account A - Region 1] A --> C[Stack Instance: Account A - Region 2] A --> D[Stack Instance: Account B - Region 1] A --> E[Stack Instance: Account B - Region 2] B --> B1[Resources] C --> C1[Resources] D --> D1[Resources] E --> E1[Resources]

Creating a Stack Set with AWS CLI


# Create a stack set
aws cloudformation create-stack-set \
  --stack-set-name SecurityBaseline \
  --template-body file://security.yaml \
  --description "Security baseline for all accounts" \
  --permission-model SERVICE_MANAGED \
  --auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false

# Add stack instances (deploy to accounts)
aws cloudformation create-stack-instances \
  --stack-set-name SecurityBaseline \
  --accounts 123456789012 098765432109 \
  --regions us-east-1 us-west-2 eu-west-1 \
  --operation-preferences MaxConcurrentCount=3,FailureToleranceCount=1

Use cases for StackSets include:

  • Deploying security baseline configurations across accounts
  • Creating consistent network resources in multiple regions
  • Setting up cross-account IAM roles for centralized management
  • Implementing organization-wide compliance controls

Custom Resources

Custom resources enable you to create resources that are not available as CloudFormation resource types.

Lambda-backed Custom Resource


Resources:
  # Lambda function that will handle the custom resource
  CustomResourceFunction:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.9
      Role: !GetAtt LambdaExecutionRole.Arn
      Handler: index.handler
      Code:
        ZipFile: |
          import json
          import cfnresponse
          import boto3
          import urllib.request
          
          def handler(event, context):
              responseData = {}
              
              try:
                  if event['RequestType'] == 'Create' or event['RequestType'] == 'Update':
                      # Example: Get the external IP address of the deployment environment
                      external_ip = urllib.request.urlopen('https://api.ipify.org').read().decode('utf8')
                      responseData['ExternalIp'] = external_ip
                      
                      # Example: Create a custom resource configuration
                      client = boto3.client('ssm')
                      client.put_parameter(
                          Name='/app/config/deployment-ip',
                          Value=external_ip,
                          Type='String',
                          Overwrite=True
                      )
                  
                  elif event['RequestType'] == 'Delete':
                      # Cleanup when stack is deleted
                      client = boto3.client('ssm')
                      client.delete_parameter(
                          Name='/app/config/deployment-ip'
                      )
                      
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
              except Exception as e:
                  print(str(e))
                  cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)})
  
  # IAM role for the function
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: AllowSSMParameterModification
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ssm:PutParameter
                  - ssm:DeleteParameter
                Resource: '*'
  
  # The custom resource itself
  DeploymentConfiguration:
    Type: Custom::DeploymentConfig
    Properties:
      ServiceToken: !GetAtt CustomResourceFunction.Arn
      # Any properties you want to pass to your Lambda function
      Environment: !Ref Environment
      
Outputs:
  DeploymentIp:
    Description: External IP used for this deployment
    Value: !GetAtt DeploymentConfiguration.ExternalIp

Dynamic References and SSM Parameters

Dynamic references allow you to reference external values stored in services like AWS Systems Manager Parameter Store or AWS Secrets Manager.

Using Dynamic References


Resources:
  MyDatabase:
    Type: AWS::RDS::DBInstance
    Properties:
      Engine: mysql
      DBName: mydatabase
      DBInstanceClass: db.t3.small
      AllocatedStorage: 20
      # Reference a database password stored in Secrets Manager
      MasterUsername: admin
      MasterUserPassword: '{{resolve:secretsmanager:MyDatabaseSecret:SecretString:password}}'
      
  MyInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      # Reference an AMI ID stored in Parameter Store
      ImageId: '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2}}'
      
  MyConfig:
    Type: AWS::AppConfig::ConfigurationProfile
    Properties:
      Name: MyAppConfig
      ApplicationId: !Ref MyAppConfigApplication
      # Reference a Parameter Store parameter with a specific version
      LocationUri: !Sub 'ssm-parameter://${AppConfigParam}:3'

CloudFormation Best Practices

Template Design

Security Considerations

Stack Policy Example


{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "Update:*",
      "Principal": "*",
      "Resource": "*"
    },
    {
      "Effect": "Deny",
      "Action": "Update:Delete",
      "Principal": "*",
      "Resource": "LogicalResourceId/ProductionDatabase"
    },
    {
      "Effect": "Deny",
      "Action": "Update:Replace",
      "Principal": "*",
      "Resource": "LogicalResourceId/ProductionDatabase"
    }
  ]
}

To apply a stack policy:


aws cloudformation set-stack-policy \
  --stack-name ProductionStack \
  --stack-policy-body file://stack-policy.json

Operational Excellence

CloudFormation Operations Best Practices

  • Version Control: Store templates in git with proper versioning
  • CI/CD Integration: Automate template validation and deployment
  • Use Change Sets: Preview changes before applying them
  • Check Drift Regularly: Detect and remediate manual changes
  • Document Stack Dependencies: Maintain documentation on stack relationships
  • Implement Tagging Strategy: Tag resources for cost allocation and ownership
  • Monitor Stack Events: Set up notifications for stack failures
  • Test Stack Deletion: Ensure stacks can be deleted cleanly in non-production
  • Create Dependency Diagrams: Visualize relationships between stacks

Real-World Example: GitOps with CloudFormation

A large financial institution implemented a GitOps approach to CloudFormation:

  1. Template Repository: All templates stored in git with branch protection
  2. CI/CD Pipeline: Automated validation, linting, and deployment
  3. Environment Promotion: Changes flow from dev → test → prod
  4. Change Review: Pull requests required for all template changes
  5. Change Sets: Generated and reviewed automatically in pipeline
  6. Stack Policy: Production stacks protected from destructive changes
  7. Drift Detection: Automated daily checks for manual changes
  8. Deployment Dashboard: Centralized view of all stacks and their status

Results: Deployment frequency increased by 300% while reducing change failures by 80%.

AWS Cloud Development Kit (CDK)

The AWS CDK is an open-source software development framework that allows you to define cloud infrastructure using familiar programming languages instead of template files.

CDK vs. Raw CloudFormation

CloudFormation Templates

  • JSON or YAML format
  • Declarative syntax
  • Limited ability to reuse code
  • Manual validation
  • Can be verbose for complex infrastructure
  • No native programming constructs

AWS CDK

  • TypeScript, JavaScript, Python, Java, C#, Go
  • Object-oriented approach
  • Strong code reuse capabilities
  • Type checking and IDE support
  • High-level abstractions
  • Full programming language features

CloudFormation vs. CDK Example

CloudFormation YAML:


Resources:
  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: MyVPC
  
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      AvailabilityZone: !Select [0, !GetAZs ""]
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: Public Subnet 1
          
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      AvailabilityZone: !Select [1, !GetAZs ""]
      CidrBlock: 10.0.2.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: Public Subnet 2
  
  # ... and much more for a complete VPC

Equivalent TypeScript CDK code:


import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';

export class NetworkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    
    // Create VPC with all the defaults
    const vpc = new ec2.Vpc(this, 'MyVPC', {
      cidr: '10.0.0.0/16',
      maxAzs: 2,
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: 'public',
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: 'private',
          subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
        }
      ]
    });
    
    // Output VPC ID
    new cdk.CfnOutput(this, 'VpcId', {
      value: vpc.vpcId
    });
  }
}

Getting Started with CDK

Setting Up CDK


# Install CDK globally
npm install -g aws-cdk

# Check the version
cdk --version

# Create a new CDK project in TypeScript
mkdir my-cdk-app
cd my-cdk-app
cdk init app --language typescript

# Deploy your CDK stack
npm run build
cdk deploy

Simple CDK App Example


// lib/my-cdk-app-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as iam from 'aws-cdk-lib/aws-iam';

export class MyCdkAppStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Create an S3 bucket
    const bucket = new s3.Bucket(this, 'MyDataBucket', {
      versioned: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });

    // Create a Lambda function
    const handler = new lambda.Function(this, 'MyLambdaHandler', {
      runtime: lambda.Runtime.NODEJS_18_X,
      code: lambda.Code.fromAsset('lambda'),
      handler: 'index.handler',
      environment: {
        BUCKET_NAME: bucket.bucketName,
      },
    });

    // Grant the Lambda function read access to the bucket
    bucket.grantRead(handler);
    
    // Output the bucket name and Lambda function ARN
    new cdk.CfnOutput(this, 'BucketName', {
      value: bucket.bucketName,
    });
    
    new cdk.CfnOutput(this, 'LambdaArn', {
      value: handler.functionArn,
    });
  }
}

CDK Best Practices

  • Use Constructs: Leverage high-level constructs for common patterns
  • Create Custom Constructs: Encapsulate reusable infrastructure patterns
  • Use Props Interface: Define clear interfaces for construct properties
  • Leverage Type Safety: Use type checking to catch errors early
  • Follow Naming Conventions: Use consistent naming for constructs and resources
  • Unit Test CDK Code: Write tests for your infrastructure code
  • Review Generated CloudFormation: Use cdk synth to examine the resulting template

Learning Activities

Activity 1: Create a Basic Web Server Stack

Create a CloudFormation template that deploys a basic web server infrastructure:

  1. Create a VPC with public and private subnets
  2. Add an Internet Gateway and route tables
  3. Deploy an EC2 instance in the public subnet with a security group allowing HTTP and SSH
  4. Install and configure a web server via user data script
  5. Create outputs for the website URL and instance ID
  6. Deploy the stack using the AWS CLI or Management Console

Activity 2: Implement Template Parameterization

Enhance your web server template with parameterization:

  1. Add parameters for environment (dev, test, prod)
  2. Add parameters for instance size with different defaults based on environment
  3. Use mapping for AMI IDs across different regions
  4. Add conditions to create different resources based on environment
  5. Implement parameter constraints and validation
  6. Deploy the stack to multiple environments with different parameter values

Activity 3: Convert to Nested Stacks

Refactor your solution using nested stacks:

  1. Create a networking stack for VPC and subnet resources
  2. Create a security stack for IAM roles and security groups
  3. Create an application stack for EC2 instances and related resources
  4. Build a parent stack that references the nested stacks
  5. Pass outputs from one nested stack to another
  6. Deploy the solution and test updating individual nested stacks

Key Takeaways

Further Learning Resources