AWS · lesson 8 of 10
CloudFormation Basics
Deploy a minimal CloudFormation stack, break it on purpose with a bad parameter, and read exactly what a failed rollback prints in the events log.
By SkillByExample · Published
CloudFormation turns a set of resources into one named, versioned unit: a template describes the desired state, and CloudFormation works out how to get there, including undoing itself cleanly if something goes wrong partway through.
This lesson runs on LocalStack via awslocal — CloudFormation with S3 and IAM
resources is well-supported in the open-source Community edition, so everything here,
including the failed rollback, needs no AWS account.
A minimal template
# stack.yaml
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
BucketName:
Type: String
Resources:
ReportsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
VersioningConfiguration:
Status: Enabled
Outputs:
BucketArn:
Value: !GetAtt ReportsBucket.Arn
Deploying it
awslocal cloudformation deploy \
--template-file stack.yaml \
--stack-name reports-stack \
--parameter-overrides BucketName=acme-reports-2026-a91f
$ awslocal cloudformation deploy --template-file stack.yaml --stack-name reports-stack --parameter-overrides BucketName=acme-reports-2026-a91f
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - reports-stack
awslocal cloudformation describe-stacks --stack-name reports-stack \
--query 'Stacks[0].{Status:StackStatus,Outputs:Outputs}'
{
"Status": "CREATE_COMPLETE",
"Outputs": [
{
"OutputKey": "BucketArn",
"OutputValue": "arn:aws:s3:::acme-reports-2026-a91f"
}
]
}
Re-running the deploy: it’s idempotent
awslocal cloudformation deploy \
--template-file stack.yaml \
--stack-name reports-stack \
--parameter-overrides BucketName=acme-reports-2026-a91f
$ awslocal cloudformation deploy --template-file stack.yaml --stack-name reports-stack --parameter-overrides BucketName=acme-reports-2026-a91f
Waiting for changeset to be created..
No changes to deploy. Stack reports-stack is up to date
Nothing was recreated — deploy diffed the current template against the running stack and
found no drift.
Breaking it on purpose
Give the bucket a name that violates S3 naming rules (uppercase letters), forcing a real failure:
# stack-broken.yaml — same template, BucketName parameter will be invalid
awslocal cloudformation deploy \
--template-file stack.yaml \
--stack-name reports-stack-broken \
--parameter-overrides BucketName=Acme_Reports_INVALID
$ awslocal cloudformation deploy --template-file stack.yaml --stack-name reports-stack-broken --parameter-overrides BucketName=Acme_Reports_INVALID
Waiting for changeset to be created..
Waiting for stack create/update to complete
Failed to create/update the stack. Run the following command
to fetch the list of events leading up to the failure
aws cloudformation describe-stack-events --stack-name reports-stack-broken
Reading the rollback
awslocal cloudformation describe-stack-events --stack-name reports-stack-broken \
--query 'StackEvents[].{Resource:LogicalResourceId,Status:ResourceStatus,Reason:ResourceStatusReason}' \
--output table
$ awslocal cloudformation describe-stack-events --stack-name reports-stack-broken --query 'StackEvents[].{Resource:LogicalResourceId,Status:ResourceStatus,Reason:ResourceStatusReason}' --output table
----------------------------------------------------------------------------------------------------------
| DescribeStackEvents |
+--------------------+-------------------------------+---------------------------------------------------+
| reports-stack-broken | ROLLBACK_COMPLETE | The following resource(s) failed to create |
| ReportsBucket | DELETE_COMPLETE | - |
| ReportsBucket | DELETE_IN_PROGRESS | - |
| reports-stack-broken | ROLLBACK_IN_PROGRESS | The following resource(s) failed to create: |
| | | [ReportsBucket] |
| ReportsBucket | CREATE_FAILED | Invalid bucket name "Acme_Reports_INVALID": |
| | | Bucket name must not contain uppercase characters |
| ReportsBucket | CREATE_IN_PROGRESS | - |
| reports-stack-broken | CREATE_IN_PROGRESS | User Initiated |
+--------------------+-------------------------------+---------------------------------------------------+
Reading bottom-to-top (events list newest first): the stack started CREATE_IN_PROGRESS,
the bucket failed with the exact S3 naming error, CloudFormation immediately began
ROLLBACK_IN_PROGRESS, deleted the bucket it had not actually finished creating, and the
stack settled at ROLLBACK_COMPLETE — not deleted, but not created either. A stack in
ROLLBACK_COMPLETE can only be deleted, never updated in place; the fix requires deleting
it and deploying fresh.
awslocal cloudformation describe-stacks --stack-name reports-stack-broken \
--query 'Stacks[0].StackStatus'
"ROLLBACK_COMPLETE"
Cleaning up
awslocal cloudformation delete-stack --stack-name reports-stack-broken
awslocal cloudformation delete-stack --stack-name reports-stack
$ awslocal cloudformation describe-stacks --stack-name reports-stack
An error occurred (ValidationError) when calling the DescribeStacks operation: Stack with
id reports-stack does not exist
Deletion removes the bucket too, in dependency order — the whole point of tracking resources as one stack rather than issuing S3 and IAM commands independently.
Practice
1. Deploy the stack a second time with a different BucketName and confirm it updates rather than recreating the stack.
awslocal cloudformation deploy --template-file stack.yaml \
--stack-name reports-stack --parameter-overrides BucketName=acme-reports-2026-b22c
awslocal cloudformation describe-stack-events --stack-name reports-stack \
--query 'StackEvents[0].ResourceStatus'
"UPDATE_COMPLETE"
UPDATE_COMPLETE, not CREATE_COMPLETE — same stack, new resource properties.
2. Generate a change set without applying it, and read what it would do.
awslocal cloudformation create-change-set --stack-name reports-stack \
--template-body file://stack.yaml \
--parameters ParameterKey=BucketName,ParameterValue=acme-reports-2026-c33d \
--change-set-name preview
awslocal cloudformation describe-change-set --change-set-name preview \
--stack-name reports-stack --query 'Changes[].ResourceChange.{Action:Action,Resource:LogicalResourceId,Replacement:Replacement}'
[
{
"Action": "Modify",
"Resource": "ReportsBucket",
"Replacement": "True"
}
]
Replacement: "True" is the important field — renaming an S3 bucket cannot be done
in-place, so this change would delete the old bucket and create a new one, losing its
contents. A change set surfaces that before anything runs.
3. Add a second resource that depends on the bucket and watch dependency-ordered rollback.
Resources:
ReportsBucket:
Type: AWS::S3::Bucket
BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref ReportsBucket
PolicyDocument: { "Version": "2012-10-17", "Statement": [] }
An empty Statement list is invalid and fails; the events log shows BucketPolicy fail
first, then ReportsBucket roll back second — CloudFormation unwinds dependents before
their dependencies.
4. List all stacks and their statuses in the account.
awslocal cloudformation list-stacks \
--query 'StackSummaries[].{Name:StackName,Status:StackStatus}'
[
{
"Name": "reports-stack",
"Status": "UPDATE_COMPLETE"
}
]
list-stacks includes deleted stacks by default for a while (DELETE_COMPLETE) unless
filtered — useful for confirming a cleanup actually finished.
5. Add a Deletion Policy so the bucket survives a stack delete.
ReportsBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
awslocal cloudformation delete-stack --stack-name reports-stack
awslocal s3 ls s3://acme-reports-2026-b22c
2026-09-13 12:10:44 0
The bucket is still there after the stack is gone — DeletionPolicy: Retain is the
standard guard for anything holding data you cannot regenerate, like a production
database or a bucket full of user uploads.
Next: CloudWatch — metrics, log groups, and an alarm that actually fires.