Infrastructure as Code

AWS CloudFormation for Beginners: Deploying Your First S3 Bucket with YAML

One template, one CLI command, one real S3 bucket — and a first look at why Infrastructure as Code isn't as simple as it sounds

I walk through writing a minimal CloudFormation template, deploying it to a live AWS account, and then intentionally breaking it to show what 'stack drift' actually looks like.

Why I'm starting with something this small

I wanted to build something with Infrastructure as Code that was small enough to fully understand in one sitting but real enough to actually deploy to an AWS account. So that's what this is: one S3 bucket, defined in a YAML file, deployed with a single CLI command. Nothing fancy.

It's tempting to skip past the basics when the end result is "just a bucket." I'd push back on that instinct. The terminology you pick up here — template, stack, logical ID, physical resource — is the vocabulary you'll be using for every CloudFormation project going forward, whether it's one resource or two hundred. Getting comfortable with it now, on something trivial, means I'm not trying to learn the concepts and debug a complex deployment at the same time later.

One honest caveat before I get into it: this is a learning exercise, not a production-grade setup. I'm deploying to a real AWS account, but I'm not covering bucket security settings, access permissions, or cost implications here — those matter, and I'd encourage anyone following along to look into things like public access blocking before deploying anything similar into an account they actually care about.

Writing the YAML template

I start with an empty file — I'm calling mine photobucket.yaml. CloudFormation supports both JSON and YAML for templates, and I'm going with YAML because it's a lot easier to read and write by hand. No braces, no trailing commas, just indentation.

Quick terminology check before writing any actual resource definitions, because this is where a lot of early confusion happens: a template is the file itself — the blueprint. A stack is what you get when you actually deploy that template — the running collection of resources AWS creates and manages on your behalf. A logical ID is the name I give a resource inside the template, purely for my own reference. A physical resource is the actual thing AWS creates in your account, which usually gets its own AWS-generated name that has nothing to do with the logical ID I chose.

As for the acronym itself — YAML apparently stands for "YAML Ain't Markup Language," a recursive backronym, though I'd double-check that against the official spec before repeating it as gospel, since acronym trivia like this is easy to get slightly wrong. Either way, the practical point stands: it's a human-friendly format built specifically so infrastructure definitions like this one are easy to read at a glance.

Defining the S3 bucket resource

With the template open, I add a Resources section and give my one resource a logical ID: photobucket. Under it, I set the Type to AWS::S3::Bucket. That's genuinely the whole minimal definition — I'm not specifying a bucket name, versioning, encryption, or any other properties.

That's intentional, and it's worth calling out explicitly: because I didn't specify a bucket name, AWS is going to generate one itself at deploy time. This is the distinction between logical ID and physical resource in practice. "photobucket" only exists inside my template as a way for me (and CloudFormation) to refer to this resource. The actual bucket that shows up in my AWS account will have some auto-generated name based on the stack name and a random suffix — not "photobucket" itself. First time I ran into this, it tripped me up a little, so I'm flagging it now so it doesn't trip you up either.

Deploying it with the AWS CLI

Template written, next step is turning it into an actual stack. I run:

aws cloudformation deploy --template-file photobucket.yaml --stack-name photodemo

Two things worth unpacking here. First, the double-dash convention: --template-file and --stack-name are long-form flags, which is the standard AWS CLI convention for anything that isn't a single-letter shorthand. Second, and more conceptually important: --stack-name is where the blueprint-versus-instance idea really clicks. The template is reusable — I could run this exact same deploy command again with a different --stack-name and get a second, entirely independent bucket and stack, with no conflict between them. The template defines what to build; the stack name defines which instance of that build I'm creating or updating.

That reusability is a big part of the appeal of IaC generally: write the definition once, then stamp out as many independent copies as you need — dev, staging, prod, whatever — just by changing the stack name (and any parameters, once templates get more sophisticated than this one).

Confirming it actually worked

The CLI command returns, and now I want to verify two things actually happened, not just that the command didn't error out.

First, I check the S3 console. Before deploying, I had 11 buckets in the account. After deploying, I've got 12. That's a small thing, but it's a genuinely satisfying confirmation — a real, physical resource just came into existence in my AWS account because of a text file and a CLI command. That's the entire promise of Infrastructure as Code in miniature.

Second, I check the CloudFormation console, where I can see the photodemo stack listed, with a creation timestamp that lines up with when I ran the deploy command. This matters because the stack and the bucket are two separate things that CloudFormation is tracking together: the stack is the management record, the bucket is the actual resource. Right now, they agree with each other — the stack says there should be a bucket, and there is one. Keep that in mind, because the next part is about what happens when they stop agreeing.

Breaking it on purpose: what stack drift looks like

Here's the part I think is actually the most useful part of this whole exercise. Instead of just cleaning up neatly, I go into the S3 console and manually delete the bucket directly — not through CloudFormation, just deleting it like I would any other bucket I no longer wanted.

Then I go back to the CloudFormation console. The photodemo stack is still sitting there, listed as active, as if nothing happened. CloudFormation doesn't know the bucket is gone. As far as the stack's records are concerned, that resource still exists and is still being managed. This mismatch — between what CloudFormation believes exists and what's actually running in the account — is called stack drift, and as I understand it, it's flagged as a real operational headache in CloudFormation, though I'd want to confirm the specifics against the current AWS documentation before treating any particular detail as settled. What I can say from what I just did is the practical version: deleting a resource outside the tool that created it doesn't clean up the tool's records, it just makes them wrong.

I'll admit I clicked through the delete confirmation instead of typing it out fully, which AWS often requires for destructive actions specifically to slow you down. That's a shortcut, not a best practice — in a real workflow, typing out the confirmation is a genuinely useful moment of friction before you do something irreversible to live infrastructure, and I wouldn't recommend skipping it out of habit. I'm going to leave a full walkthrough of resolving stack drift for a follow-up, since it deserves its own space rather than a rushed explanation tacked onto the end of this one.

Cleaning up properly

To reset things cleanly, I delete the entire photodemo stack rather than leaving it in its drifted state. This is the correct way to tear down CloudFormation-managed infrastructure — delete the stack, and CloudFormation attempts to remove everything it believes it's managing under that stack, rather than me hunting down and manually deleting resources one at a time.

It's a small detail, but it reinforces the core mental model of the whole exercise: the stack, not the individual resource, is the unit you're supposed to be operating on. Deleting resources directly, like I did earlier to demonstrate drift, is the exception you do on purpose to see what breaks — not the normal path.

Conclusion

What I like about this exercise is that it's small enough to hold entirely in your head, but it still surfaces a real operational failure mode — stack drift — that you'll eventually run into on far more complicated infrastructure. I'd stop short of calling one demo bucket a resume line item; it's a genuinely useful first milestone, not a claim of production IaC experience. If you want to get a feel for it yourself, the natural next step is writing this same template in a sandbox AWS account, deploying it, and watching the bucket count change — just make sure you've thought about basic bucket security and cost settings before you point similar templates at an account you actually care about.

Official AWS references

KEEP PRACTICING THE DECISION

Learn the concept, then test the edge case.

The Solutions Architect Associate course in TutorialRepo combines guided lessons, scenario questions, review, and course-aware explanations.

  All articles