CloudFormation lets you define AWS infrastructure in YAML or JSON templates. You describe what you want; CloudFormation figures out how to create it in the right order, handles dependencies, and rolls back on failure.
Real-World: Your startup needs to spin up identical environments for dev, staging, and production. With CloudFormation, one template creates all three — VPC, subnets, RDS, ElastiCache, EC2 ASG, ALB, Lambda. Run the same template 3 times with different parameter values.
AWSTemplateFormatVersion: '2010-09-09'
Description: 'My Application Stack'
Parameters: # Input values at deploy time
Environment:
Type: String
AllowedValues: [dev, staging, prod]
Default: dev
InstanceType:
Type: String
Default: t3.micro
Mappings: # Static lookup tables
EnvConfig:
dev:
MinSize: 1
MaxSize: 2
prod:
MinSize: 3
MaxSize: 10
Conditions: # Boolean expressions
IsProd: !Equals [!Ref Environment, prod]
Resources: # REQUIRED - the actual AWS resources
MyBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub 'my-app-${Environment}-${AWS::AccountId}'
VersioningConfiguration:
Status: !If [IsProd, Enabled, Suspended]
MyDB:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceClass: !If [IsProd, db.t3.large, db.t3.micro]
MasterUsername: admin
MasterUserPassword: !Sub '{{resolve:secretsmanager:${Environment}/db-password}}'
Outputs: # Values to export or display
BucketName:
Value: !Ref MyBucket
Export:
Name: !Sub '${AWS::StackName}-BucketName'
DBURL:
Value: !GetAtt MyDB.Endpoint.Address| Function | Purpose | Example |
|---|---|---|
!Ref |
Reference parameter or resource | !Ref MyBucket |
!GetAtt |
Get attribute of resource | !GetAtt MyLB.DNSName |
!Sub |
String substitution | !Sub 'bucket-${AWS::AccountId}' |
!If |
Conditional value | !If [IsProd, big, small] |
!ImportValue |
Import output from another stack | !ImportValue VPC-SubnetId |
!Join |
Join values | !Join [',', [a, b, c]] |
!Select |
Select from list | !Select [0, !GetAZs ''] |
!FindInMap |
Lookup in Mappings | !FindInMap [EnvConfig, !Ref Env, MinSize] |
!Cidr |
Generate CIDR blocks | Used in VPC subnet design |
!Base64 |
Encode to Base64 | EC2 UserData |
AWS::AccountId # 123456789012
AWS::Region # us-east-1
AWS::StackName # my-app-stack
AWS::StackId # arn:aws:cloudformation:...
AWS::NoValue # Remove property (used with Conditions)
AWS::URLSuffix # amazonaws.com (or cn for China)Developer updates template → creates Change Set → reviews proposed changes
→ executes Change Set → stack updated
Never apply directly to production without reviewing the Change Set. A Change Set shows:
- Resources being added/modified/deleted
- Resources that will be replaced (risky!)
- No changes until you execute
# Create change set
aws cloudformation create-change-set \
--stack-name my-stack \
--template-body file://template.yaml \
--change-set-name my-update
# Review
aws cloudformation describe-change-set --change-set-name my-update
# Apply
aws cloudformation execute-change-set --change-set-name my-updatePrevent accidental update/deletion of critical resources:
{
"Statement": [
{
"Effect": "Deny",
"Action": "Update:Replace",
"Principal": "*",
"Resource": "LogicalResourceId/ProductionDatabase"
},
{
"Effect": "Allow",
"Action": "Update:*",
"Principal": "*",
"Resource": "*"
}
]
}This prevents the RDS instance from being replaced during stack updates.
Default: If any resource fails → rollback ALL resources.
Rollback options:
ROLLBACK_ALL_FAILURES: rollback on any failure (default)DO_NOTHING: keep partially created resources (useful for debugging)- Specific resources can have
DeletionPolicy
MyDB:
Type: AWS::RDS::DBInstance
DeletionPolicy: Retain # Keep RDS even if stack deleted
# Options: Delete (default), Retain, Snapshot
MyBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Snapshot # Take snapshot before delete (not valid for S3)Critical: S3 buckets with contents will fail to delete. Use DeletionPolicy: Retain or custom resource to empty bucket first.
When you need to do something CloudFormation doesn't natively support:
Stack create/update/delete → SNS/Lambda → your custom code → return response to CloudFormation
Use cases:
- Populate DynamoDB table with initial data
- Create Route 53 records in external DNS
- Rotate secrets during deployment
- Empty S3 bucket before deletion
EmptyBucketResource:
Type: Custom::EmptyBucket
Properties:
ServiceToken: !GetAtt EmptyBucketFunction.Arn
BucketName: !Ref MyBucketLambda receives RequestType: Create/Update/Delete and must call CloudFormation callback URL.
Break large templates into smaller, reusable ones:
RootStack
├── NetworkStack (VPC, subnets, IGW)
├── SecurityStack (Security groups, NACLs)
├── DatabaseStack (RDS, parameter group)
└── AppStack (EC2 ASG, ALB)
NetworkStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/templates/network.yaml
Parameters:
VpcCidr: 10.0.0.0/16Cross-stack references: Parent stack passes outputs to child stacks via Parameters. Child stacks export outputs that parent can reference.
Deploy same template to multiple AWS accounts and regions:
Management Account
└── StackSet: security-baseline
├── Deploy to: Account A (us-east-1, eu-west-1)
├── Deploy to: Account B (us-east-1)
└── Deploy to: Account C (ap-southeast-1, us-east-1)
Use cases:
- Deploy security baseline (CloudTrail, Config, GuardDuty) to all accounts
- Standardize networking across org
Detect if someone manually changed a resource outside CloudFormation:
Template says: S3 bucket versioning = Enabled
Someone went to console and disabled versioning
Drift detection: "MODIFIED - BucketVersioningConfiguration"
Fix: Update template OR use aws cloudformation detect-stack-resource-drift + remediate.
For configuring EC2 instances:
MyEC2:
Type: AWS::EC2::Instance
Metadata:
AWS::CloudFormation::Init:
config:
packages:
yum:
httpd: []
files:
/var/www/html/index.html:
content: '<h1>Hello from CloudFormation</h1>'
services:
sysvinit:
httpd:
enabled: true
ensureRunning: true
Properties:
UserData: !Base64
!Sub |
#!/bin/bash
/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource MyEC2 --region ${AWS::Region}
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource MyEC2 --region ${AWS::Region}cfn-signal tells CloudFormation the instance is ready (required for WaitCondition).
| Practice | Reason |
|---|---|
| Review Change Sets before applying | Know exactly what's changing |
| Use DeletionPolicy: Retain on critical resources | Prevent accidental data loss |
| Use nested stacks for large templates | Modularity, reusability |
| Use Stack Policies | Protect critical resources from updates |
| Parameterize environment-specific values | Same template for dev/prod |
Use !Sub over !Join for readability |
Cleaner string substitution |
| Store templates in S3 with versioning | Rollback to previous template |
| Anti-Pattern | Impact | Fix |
|---|---|---|
| Hardcoding account IDs/region | Template not portable | Use AWS::AccountId, AWS::Region |
No DeletionPolicy on RDS/S3 |
Data loss on stack delete | Add DeletionPolicy: Retain or Snapshot |
| Directly editing resources in console | Drift, CloudFormation loses sync | Always change through CloudFormation |
| Putting secrets in template Parameters | Secrets visible in console/logs | Use NoEcho: true + reference Secrets Manager |
| One huge 5000-line template | Hard to maintain, update errors cascade | Use nested stacks |
!Refon a resource returns the resource ID/name.!Refon a parameter returns the value.!GetAttgets a specific attribute (like the endpoint, ARN, etc.)- Change Sets don't apply until you execute them.
- DeletionPolicy options:
Delete(default),Retain,Snapshot(only DB/EFS/ElastiCache). cfn-signalis needed when usingCreationPolicyon EC2 — tells CloudFormation instance bootstrapped successfully.- Stack Sets require: either service-managed (AWS Organizations) or self-managed (create execution roles in each account).
- CloudFormation limits: 200 stacks per account per region (default), 500 resources per template.
Q: Stack update failing because RDS is being replaced — prevent it? → Use Stack Policy to deny Replace on that resource.
Q: Delete stack but keep the RDS instance?
→ Set DeletionPolicy: Retain on the RDS resource.
Q: Deploy same infrastructure across 20 AWS accounts? → StackSets with AWS Organizations integration.
Q: CloudFormation resource that runs custom code? → Custom Resource backed by Lambda.
Q: Preview impact of template change before applying? → Create a Change Set and review before executing.