Overview
AWS CloudFormation is Amazon’s native infrastructure as code service that allows you to model, provision, and manage AWS resources by writing declarative templates in JSON or YAML. You describe your desired infrastructure state in a template, and CloudFormation handles the creation, updating, and deletion of resources in the correct order, managing dependencies automatically. CloudFormation is free to use—you only pay for the resources it creates.
CloudFormation organizes resources into stacks, which are deployed as a single unit. Stacks can reference other stacks through nested stacks or cross-stack references using exports. Change sets let you preview modifications before applying them. CloudFormation supports drift detection to identify manual changes, stack policies to protect critical resources, and StackSets for deploying across multiple accounts and regions. The CloudFormation Registry extends support to third-party resources through custom resource providers.
Installation
AWS CLI Setup
# Install AWS CLI
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
# Configure credentials
aws configure
# Install cfn-lint for template validation
pip install cfn-lint
# Install rain (improved CloudFormation CLI)
brew install rain
Core Commands
| Command | Description |
|---|
aws cloudformation create-stack | Create a new stack |
aws cloudformation update-stack | Update an existing stack |
aws cloudformation delete-stack | Delete a stack |
aws cloudformation describe-stacks | List/describe stacks |
aws cloudformation describe-stack-events | View stack events |
aws cloudformation validate-template | Validate template syntax |
aws cloudformation create-change-set | Preview changes before applying |
aws cloudformation detect-stack-drift | Check for manual changes |
aws cloudformation list-exports | List cross-stack exports |
aws cloudformation package | Package local artifacts for deployment |
Stack Operations
# Create stack
aws cloudformation create-stack \
--stack-name my-app \
--template-body file://template.yaml \
--parameters ParameterKey=Environment,ParameterValue=production \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
--tags Key=Project,Value=MyApp
# Update stack
aws cloudformation update-stack \
--stack-name my-app \
--template-body file://template.yaml \
--parameters ParameterKey=Environment,ParameterValue=production
# Delete stack
aws cloudformation delete-stack --stack-name my-app
# Wait for stack creation
aws cloudformation wait stack-create-complete --stack-name my-app
# Describe stack outputs
aws cloudformation describe-stacks --stack-name my-app --query 'Stacks[0].Outputs'
Change Sets
# Create change set
aws cloudformation create-change-set \
--stack-name my-app \
--change-set-name update-v2 \
--template-body file://template.yaml
# Describe change set
aws cloudformation describe-change-set \
--stack-name my-app \
--change-set-name update-v2
# Execute change set
aws cloudformation execute-change-set \
--stack-name my-app \
--change-set-name update-v2
Template Structure
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Production web application infrastructure'
Parameters:
Environment:
Type: String
Default: dev
AllowedValues: [dev, staging, production]
InstanceType:
Type: String
Default: t3.micro
AllowedValues: [t3.micro, t3.small, t3.medium]
Mappings:
RegionAMI:
us-east-1:
AMI: ami-0abcdef1234567890
us-west-2:
AMI: ami-0fedcba0987654321
Conditions:
IsProduction: !Equals [!Ref Environment, production]
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-vpc'
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: true
WebServer:
Type: AWS::EC2::Instance
Properties:
InstanceType: !If [IsProduction, t3.medium, !Ref InstanceType]
ImageId: !FindInMap [RegionAMI, !Ref 'AWS::Region', AMI]
SubnetId: !Ref PublicSubnet
SecurityGroupIds:
- !Ref WebSG
UserData:
Fn::Base64: !Sub |
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
WebSG:
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/0
DataBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
Outputs:
VpcId:
Value: !Ref VPC
Export:
Name: !Sub '${AWS::StackName}-VpcId'
WebServerIP:
Value: !GetAtt WebServer.PublicIp
Description: Public IP of the web server
Intrinsic Functions
| Function | Usage | Description |
|---|
!Ref | !Ref MyResource | Returns resource ID or parameter value |
!GetAtt | !GetAtt MyBucket.Arn | Get resource attribute |
!Sub | !Sub 'arn:aws:s3:::${Bucket}' | String substitution |
!Join | !Join ['-', [a, b, c]] | Join strings with delimiter |
!Select | !Select [0, !GetAZs ''] | Select item from list |
!Split | !Split [',', 'a,b,c'] | Split string into list |
!If | !If [IsProd, t3.large, t3.micro] | Conditional value |
!ImportValue | !ImportValue SharedVpcId | Cross-stack reference |
!FindInMap | !FindInMap [Map, Key1, Key2] | Lookup in mappings |
Configuration
Stack Policy
{
"Statement": [
{
"Effect": "Allow",
"Action": "Update:*",
"Principal": "*",
"Resource": "*"
},
{
"Effect": "Deny",
"Action": "Update:Replace",
"Principal": "*",
"Resource": "LogicalResourceId/Database"
}
]
}
aws cloudformation set-stack-policy \
--stack-name my-app \
--stack-policy-body file://policy.json
Advanced Usage
StackSets (Multi-Account/Region)
# Create stack set
aws cloudformation create-stack-set \
--stack-set-name security-baseline \
--template-body file://baseline.yaml \
--permission-model SERVICE_MANAGED \
--auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false
# Deploy to accounts
aws cloudformation create-stack-instances \
--stack-set-name security-baseline \
--deployment-targets OrganizationalUnitIds=ou-abc123 \
--regions us-east-1 us-west-2 eu-west-1
Nested Stacks
Resources:
NetworkStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/mybucket/network.yaml
Parameters:
VpcCidr: 10.0.0.0/16
AppStack:
Type: AWS::CloudFormation::Stack
DependsOn: NetworkStack
Properties:
TemplateURL: https://s3.amazonaws.com/mybucket/app.yaml
Parameters:
VpcId: !GetAtt NetworkStack.Outputs.VpcId
Drift Detection
# Detect drift
aws cloudformation detect-stack-drift --stack-name my-app
# Check drift status
aws cloudformation describe-stack-drift-detection-status \
--stack-drift-detection-id <id>
# View drifted resources
aws cloudformation describe-stack-resource-drifts \
--stack-name my-app \
--stack-resource-drift-status-filters MODIFIED DELETED
Troubleshooting
| Issue | Solution |
|---|
ROLLBACK_COMPLETE state | Stack failed creation; delete it and fix the template errors before re-creating |
UPDATE_ROLLBACK_FAILED | Use continue-update-rollback with --resources-to-skip for stuck resources |
| Circular dependency | Use DependsOn carefully; break cycles with !ImportValue across stacks |
| Resource replacement unexpected | Check change set before applying; some property changes force replacement |
CAPABILITY_IAM required | Add --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM to create/update commands |
| Template validation fails | Run cfn-lint template.yaml for detailed error messages |
| Stack deletion stuck | Check for S3 buckets with data or ENIs attached to Lambda in VPC |