You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
Writing CloudFormation templates that work is one thing — writing templates that are secure, maintainable, and production-ready is another. In this final lesson, we will cover the best practices that experienced cloud engineers follow when working with CloudFormation. These practices will help you avoid common pitfalls, reduce risk, and build infrastructure that stands the test of time.
Every CloudFormation template should be stored in a Git repository. This provides:
Never store templates only in S3 or on a local machine. Git is your single source of truth.
Direct stack updates (update-stack) skip the review step. In production, always use change sets:
# Create the change set
aws cloudformation create-change-set \
--stack-name prod-stack \
--change-set-name release-v2 \
--template-body file://template.yaml
# Review it
aws cloudformation describe-change-set \
--stack-name prod-stack \
--change-set-name release-v2
# Execute only if the changes are correct
aws cloudformation execute-change-set \
--stack-name prod-stack \
--change-set-name release-v2
Change sets are your safety net — use them every time.
For any stack that should not be accidentally deleted, enable termination protection:
aws cloudformation update-termination-protection \
--stack-name production-database \
--enable-termination-protection
This prevents accidental delete-stack calls from destroying production infrastructure.
Any resource that stores data — databases, S3 buckets, EBS volumes — should have a DeletionPolicy:
Resources:
ProductionDB:
Type: AWS::RDS::DBInstance
DeletionPolicy: Snapshot
UpdateReplacePolicy: Snapshot
Properties:
Engine: postgres
DBInstanceClass: db.r5.large
AllocatedStorage: '100'
AuditLogs:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
Properties:
BucketName: !Sub '${AWS::StackName}-audit-logs'
Without a DeletionPolicy, deleting the stack permanently destroys the data.
Hard-coded values make templates inflexible. Use parameters for anything that varies between environments:
Parameters:
Environment:
Type: String
AllowedValues: [dev, staging, production]
InstanceType:
Type: String
Default: t3.micro
MinInstances:
Type: Number
Default: 1
A single template should be deployable to dev, staging, and production with different parameter values.
A stack policy prevents accidental modification or replacement of specific resources:
{
"Statement": [
{
"Effect": "Deny",
"Action": "Update:Replace",
"Principal": "*",
"Resource": "LogicalResourceId/ProductionDB"
},
{
"Effect": "Allow",
"Action": "Update:*",
"Principal": "*",
"Resource": "*"
}
]
}
Apply it during stack creation:
aws cloudformation create-stack \
--stack-name prod-stack \
--template-body file://template.yaml \
--stack-policy-body file://policy.json
Always validate your templates before deploying them:
# Basic syntax validation
aws cloudformation validate-template \
--template-body file://template.yaml
# Use cfn-lint for deeper checks
pip install cfn-lint
cfn-lint template.yaml
cfn-lint catches many issues that validate-template misses, including:
Export values that other stacks genuinely need, but do not over-export:
Outputs:
VpcId:
Value: !Ref VPC
Export:
Name: !Sub '${AWS::StackName}-VpcId'
Remember: you cannot delete a stack whose exports are imported by another stack. Over-exporting creates tight coupling between stacks.
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.