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.
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.
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.
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.
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
- Use YAML: More readable and supports comments, compared to JSON
- Parameterize Templates: Make templates reusable across environments
- Validate Templates: Use the aws cloudformation validate-template command before deployment
- Document Resources: Add comments and descriptions to clarify resource purpose
- Logical Structure: Organize resource definitions in a logical order
- Use Nested Stacks: Break down complex infrastructure into manageable components
- Include Outputs: Define stack outputs for important resource properties
- Consistent Naming: Use a consistent naming convention for resources
Security Considerations
- Avoid Hardcoded Secrets: Use dynamic references for credentials
- Principle of Least Privilege: Minimize IAM permissions for stacks
- Enable Stack Policy: Protect critical resources from unintended updates
- Use Service Roles: Deploy stacks with specific IAM roles
- Store Templates Securely: Keep templates in access-controlled repositories
- Validate with Change Sets: Preview changes before applying to production stacks
- Monitor Stack Events: Set up notifications for stack operations
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:
- Template Repository: All templates stored in git with branch protection
- CI/CD Pipeline: Automated validation, linting, and deployment
- Environment Promotion: Changes flow from dev → test → prod
- Change Review: Pull requests required for all template changes
- Change Sets: Generated and reviewed automatically in pipeline
- Stack Policy: Production stacks protected from destructive changes
- Drift Detection: Automated daily checks for manual changes
- 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:
- Create a VPC with public and private subnets
- Add an Internet Gateway and route tables
- Deploy an EC2 instance in the public subnet with a security group allowing HTTP and SSH
- Install and configure a web server via user data script
- Create outputs for the website URL and instance ID
- Deploy the stack using the AWS CLI or Management Console
Activity 2: Implement Template Parameterization
Enhance your web server template with parameterization:
- Add parameters for environment (dev, test, prod)
- Add parameters for instance size with different defaults based on environment
- Use mapping for AMI IDs across different regions
- Add conditions to create different resources based on environment
- Implement parameter constraints and validation
- Deploy the stack to multiple environments with different parameter values
Activity 3: Convert to Nested Stacks
Refactor your solution using nested stacks:
- Create a networking stack for VPC and subnet resources
- Create a security stack for IAM roles and security groups
- Create an application stack for EC2 instances and related resources
- Build a parent stack that references the nested stacks
- Pass outputs from one nested stack to another
- Deploy the solution and test updating individual nested stacks
Key Takeaways
- CloudFormation provides AWS-native infrastructure as code capabilities
- Templates define resources in JSON or YAML format with declarative syntax
- Stacks are instantiated templates that create and manage actual AWS resources
- Change sets allow you to preview infrastructure changes before implementing them
- Intrinsic functions add logic and references to templates
- Nested stacks and stack sets provide scalable management of complex infrastructure
- The AWS CDK offers a programming language approach to CloudFormation
- CloudFormation integrates deeply with the AWS ecosystem and IAM permissions