CoursesAWS CloudFormationWhat CloudFormation is & the model

What CloudFormation is & the model

Templates, stacks, managed provisioning.

Beginner12 min · lesson 1 of 12

An architect does not lay the bricks. They draw the finished building, hand the drawing to a builder, and the builder works out the order: foundation first, then the walls, then the roof. AWS CloudFormation (Amazon Web Services' built-in infrastructure-as-code service) works the same way. You write a template, a plain-text document that describes the cloud resources you want, and CloudFormation reads it, figures out the order, and builds everything by calling Amazon's own APIs (application programming interfaces, the command channels each AWS service listens on). Infrastructure as code means your servers, networks, and permissions live in a file you can read, review, and check into version control, instead of a pile of console clicks nobody remembers making.

Because CloudFormation is part of AWS, there is nothing to install and no separate state file to babysit. AWS stores the record of what it built, tracks every resource, and knows how to create, update, and delete each one. That native position is the whole pitch against an outside tool. CloudFormation learns each AWS resource's lifecycle first-hand, orders the dependencies for you, and rewinds automatically when a build fails partway through. It is also the foundation under the higher-level tools you will meet later: the Serverless Application Model (SAM) and the Cloud Development Kit (CDK), which turns real programming languages into CloudFormation templates behind the scenes. The price of all this is lock-in. CloudFormation speaks AWS, and only AWS.

Templates, stacks, and resources

Three words carry the whole model. A template is the blueprint: a declarative document, which means you write down the end state you want (this bucket, that database, these permissions) and let CloudFormation work out the steps to get there. You never write 'create the bucket, then wait, then attach the policy.' You describe the finished shape and hand it over. Templates are written in YAML or JSON (two plain-text formats for structured data; YAML is the easier of the two to read). A resource is one thing inside that blueprint: a single Amazon S3 bucket (Simple Storage Service, AWS's object storage), one EC2 virtual machine (Elastic Compute Cloud), one IAM role (Identity and Access Management, the permission system). A stack is the finished building. It is the live set of resources CloudFormation created from one template and now manages together. Everything in a stack is born together, changes together, and, if you delete the stack, dies together, like a labeled crate on moving day: whatever went in comes out together when you empty it.

s3-bucket.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: One private S3 bucket, managed as a stack
Resources:
DataBucket:
Type: AWS::S3::Bucket
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true

The only section CloudFormation actually requires is Resources; the version line and description are optional politeness. DataBucket is the logical ID, your private name for the resource inside the template. Notice this bucket is locked down on purpose: encryption switched on by default, every public-access door bolted shut. Writing that into the blueprint means every copy you ever build starts safe, and any future attempt to loosen it has to show up as a change to a file someone can review.

From template to running infrastructure

Hand the template to CloudFormation and it becomes a stack. One command does it.

terminal
aws cloudformation create-stack \
--stack-name demo-bucket \
--template-body file://s3-bucket.yaml
output
{
"StackId": "arn:aws:cloudformation:us-east-1:123456789012:stack/demo-bucket/8f2a1c40-85d1-11f0-b3e9-0e6c2f5a1b23"
}

That Amazon Resource Name (ARN, AWS's globally unique address for a thing) is the stack's permanent handle. The command returns right away, because CloudFormation does the real work in the background. To follow along, ask the stack for its status. (If you would rather block until it finishes, aws cloudformation wait stack-create-complete --stack-name demo-bucket does exactly that.)

terminal
aws cloudformation describe-stacks \
--stack-name demo-bucket \
--query 'Stacks[0].StackStatus' --output text
output
CREATE_COMPLETE

A stack moves through a small vocabulary of states: CREATE_IN_PROGRESS while it builds, CREATE_COMPLETE when every resource is up, and a set of ROLLBACK states when something goes wrong. Those words are the contract your scripts and pipelines check before they trust a stack. Underneath, CloudFormation did the managed provisioning for you: it read the template, decided what depended on what, called the S3 API to create the bucket, waited for AWS to confirm, and wrote the result into the stack's own records. You made zero API calls by hand. You described the end state, and the service made it real, in the right order.

How a template becomes a running stack
1Write a template
Declare the resources you want, in YAML or JSON
2create-stack
Hand the template to CloudFormation
3Plan the order
Read dependencies, decide what to build first
4Call the AWS APIs
Create each resource, record logical to physical ID
5Emit events, reach a status
Audit trail; CREATE_COMPLETE, or roll back on failure

The stack keeps a ledger

When you named the bucket DataBucket, AWS did not actually call it that. Ask the stack what it really built.

terminal
aws cloudformation list-stack-resources \
--stack-name demo-bucket \
--query 'StackResourceSummaries[].[LogicalResourceId,PhysicalResourceId,ResourceStatus]' \
--output text
output
DataBucket demo-bucket-databucket-1a2b3c4d5e6f CREATE_COMPLETE

DataBucket is the logical ID, the nickname you use in the template. demo-bucket-databucket-1a2b3c4d5e6f is the physical ID, the real name AWS handed out. The stack holds the mapping between the two, and that mapping is the quiet center of the whole model. Change the template later and run an update, and CloudFormation looks up DataBucket, finds the exact bucket behind it, and changes that one instead of making a second. The logical ID is how you point at a resource for its whole life without ever hard-coding its real name, the shipping label you wrote (Gift A) versus the tracking number the courier assigned. One warning follows from this: some properties cannot be changed in place. Edit a bucket's BucketName and CloudFormation cannot rename a bucket that already exists, so it builds a new empty bucket, swaps the stack's mapping onto it, and tries to remove the old one. Your application now reads from an empty bucket, and the data in the old one does not follow. (If that old bucket still holds objects, S3 refuses to delete it, so CloudFormation leaves it orphaned outside the stack, unmanaged and still costing you money.)

Other infrastructure-as-code tools, like Terraform, keep this same ledger in a state file that you have to store somewhere safe and lock so two people cannot edit it at once. CloudFormation keeps the ledger inside AWS. There is no state file to lose, leak, or corrupt, and the locking is handled for you. Since that record can hold sensitive values, keeping it inside AWS under IAM permissions instead of a file on someone's laptop is one less thing that can spill.

When a build fails, the whole thing rewinds

A bank transfer either completes in full or not at all. It never strands half the money in transit. CloudFormation aims for that same all-or-nothing behavior, and it is the biggest reason to prefer it over a shell script that fires AWS commands one after another. Add a second bucket to the template with a name that is already taken (S3 bucket names are global, shared across every AWS customer on the planet) and watch what CloudFormation does.

two-buckets.yaml
AWSTemplateFormatVersion: "2010-09-09"
Resources:
DataBucket:
Type: AWS::S3::Bucket
LogBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: logs # global name, already taken: this create fails
terminal
aws cloudformation create-stack \
--stack-name demo-two \
--template-body file://two-buckets.yaml
output
{
"StackId": "arn:aws:cloudformation:us-east-1:123456789012:stack/demo-two/1b7d9e10-85d3-11f0-9a2c-0e6c2f5a1b23"
}

Now read the stack's event stream, oldest first.

terminal
aws cloudformation describe-stack-events --stack-name demo-two \
--query "reverse(StackEvents[?ResourceStatusReason!='Resource creation Initiated'].[Timestamp,LogicalResourceId,ResourceStatus])" \
--output text
output
2026-07-21T11:02:01.104000+00:00 demo-two CREATE_IN_PROGRESS
2026-07-21T11:02:04.882000+00:00 DataBucket CREATE_IN_PROGRESS
2026-07-21T11:02:05.271000+00:00 LogBucket CREATE_IN_PROGRESS
2026-07-21T11:02:07.930000+00:00 LogBucket CREATE_FAILED
2026-07-21T11:02:26.017000+00:00 DataBucket CREATE_COMPLETE
2026-07-21T11:02:29.660000+00:00 demo-two ROLLBACK_IN_PROGRESS
2026-07-21T11:02:31.928000+00:00 DataBucket DELETE_IN_PROGRESS
2026-07-21T11:02:33.114000+00:00 DataBucket DELETE_COMPLETE
2026-07-21T11:02:34.550000+00:00 demo-two ROLLBACK_COMPLETE

Read it top to bottom. LogBucket failed within a couple of seconds, because its name was already taken. DataBucket was still being built at that moment, and CloudFormation does not rip a half-finished resource out from under itself, so it let DataBucket reach CREATE_COMPLETE first. Then, with one bucket standing and one dead, the stack flipped to ROLLBACK_IN_PROGRESS and went back to delete DataBucket, the resource that had actually worked, leaving you at ROLLBACK_COMPLETE with nothing half-built. To find out why LogBucket died, pull the reason CloudFormation recorded.

terminal
aws cloudformation describe-stack-events --stack-name demo-two \
--query "StackEvents[?ResourceStatus=='CREATE_FAILED'].ResourceStatusReason" \
--output text
output
Resource handler returned message: "The requested bucket name is not available. The bucket namespace is shared by all users of the system. Please select a different name and try again. (Service: S3, Status Code: 409, Request ID: 8F3KQ2ZP4NDABC12, Extended Request ID: 9m2Lk3n1oP0qRsTuVwXyZaBcDeFgHiJkLmNoPqRsTuVwXyZ012=)" (RequestToken: 3f7c9a21-2f0e-4d5b-9c1a-7b6e2d4a1c88, HandlerErrorCode: AlreadyExists)

That failure reason is written into the permanent event log, not printed once and lost. For a defender or an operator, this is the payoff: every create, update, and delete leaves a timestamped trail of what changed and why, and a failed deploy backs itself out instead of leaving a half-configured network with a hole in it.

What the model buys you

Three habits fall out of this design, and all three matter for security work. First, the event stream is an audit trail. Pair it with CloudTrail (AWS's log of every API call and the identity behind it) and you can answer both what changed, from the stack events, and who asked for it, from CloudTrail. Second, before an update ever touches production, a change set lets you preview it. aws cloudformation create-change-set stages the diff, and aws cloudformation describe-change-set prints it, including any resource marked Replacement: True, which means it will be destroyed and rebuilt. Reading that list is your last chance to catch a rename that would wipe a database. Third, drift detection tells you when someone changed a resource outside the stack, editing it by hand in the console, so a manual or unauthorized change cannot hide for long.

A stack delete can take your database with it
Because the stack owns everything it made, deleting the stack deletes those resources in dependency order, and a mistyped delete-stack against production can drop a live database in seconds. Two guardrails. Put DeletionPolicy: Retain (or Snapshot for databases) on anything you cannot lose, so CloudFormation leaves the real resource alive even after the stack is gone. And turn on termination protection with aws cloudformation update-termination-protection --enable-termination-protection --stack-name prod, so the stack refuses to be deleted at all until you deliberately switch it off. Editing a stack's resources by hand in the console is the other trap: it creates drift, and the next stack update may quietly undo your change. Treat the template as the source of truth and change your infrastructure through the stack, not around it.
Quick check
01You run create-stack and CloudFormation builds 6 of 10 resources, then resource number 7 fails. With default settings, where do you end up?
Correct — the default is all-or-nothing, so a failed create unwinds the whole stack rather than leaving a partial build.
Incorrect — That is how an ad-hoc shell script behaves; CloudFormation instead reverses the resources it already created.
Incorrect — Rollback does not force a genuinely failing resource to succeed; it backs the stack out.
Incorrect — The stack record and its event log stay at ROLLBACK_COMPLETE precisely so you can read why it failed.
02CloudFormation records what a stack built inside AWS itself, rather than in a separate state file the way a tool like Terraform does. What practical consequence does that native record have for you?
Incorrect — the whole point is that there is no separate state file for you to store or lock; AWS keeps the record.
Correct — because the ledger lives inside AWS under its permissions, there is nothing on a laptop to spill and no lock for you to manage.
Incorrect — the record is durable; the stack tracks its resources for their whole life, not just during a deploy.
Incorrect — the record can hold sensitive values, and keeping it inside AWS under IAM is safer than a local file, not impossible.
03An existing S3 bucket in a stack was created with a generated name and now holds live objects. A teammate adds a hard-coded BucketName to that resource and you run an update. What actually happens? (S3 is Amazon's Simple Storage Service.)
Incorrect — a bucket cannot be renamed in place, so CloudFormation cannot honor the change that way.
Incorrect — the update is not blocked; CloudFormation proceeds and builds a replacement bucket.
Correct — the rename forces a replacement; your app now reads the empty new bucket, and the non-empty old one is orphaned outside the stack and keeps costing money.
Incorrect — CloudFormation does not migrate objects; the data in the old bucket does not follow the rename.

After any deploy, whether it went right or wrong, aws cloudformation describe-stack-events is the first place to look. It is the closest thing your infrastructure has to a flight recorder: every resource, every status, every reason, in order, kept by AWS whether the stack finished or rolled back. Learn to read it, and most CloudFormation mysteries answer themselves.

Try this

Work through “What the model buys you” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.

Takeaway

The trap worth remembering here: a stack delete can take your database with it. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.

Related