From Manual Setup to IaC: Stop Clicking the AWS Console

An EC2 instance here, an S3 bucket with a specific config there, a security group someone tweaked manually six months ago. The console is stateless - it doesn't remember intent, it just shows current state. And "current state" drifts from what was actually planned.
CloudFormation is AWS's answer to this. Instead of clicking through menus, you write a template that describes your infrastructure. That template becomes the source of truth - reviewable, versioned, auditable, and repeatable. This is what Infrastructure as Code actually means in practice.
What Infrastructure as Code Actually Means
A proper IaC tool has to be:
Declarative - the template describes what you want, not how to get there. You don't write step-by-step instructions; you describe the desired end state. CloudFormation reads your template, figures out what API calls are needed, and makes them. If you look at the template, you're looking at a direct representation of your infrastructure. What you see is what you have.
Versioned - the templates are files. You get history, code review, rollback, audit trails. changes are tracked in VCS.
CLI commands are imperative and ephemeral. You run a command, something gets created, but there's no record of intent and nothing to review. For quick one-off tasks, CLI is faster. For managing real infrastructure, you need something declarative.
CFT vs. CLI - When to Use Which
| Situation | Use |
|---|---|
| Quick lookup - "all S3 buckets" | CLI |
| Creating a full infrastructure stack (AWS) | CloudFormation |
| Multi-cloud or hybrid cloud environment | Terraform |
CloudFormation only works with AWS. If your org is AWS-only, it's a perfectly valid choice. If you're working across AWS and Azure, or planning to, use Terraform instead - it handles multi-cloud.
The Structure of a CloudFormation Template
Templates are written in YAML or JSON. YAML is mostly preferred and is already familiar from Kubernetes and Ansible. We'll stick to YAML.
Here's the full structure:
AWSTemplateFormatVersion: "2010-09-17"
Description: This is a sample template
Parameters:
InstanceType:
Type: String
Default: t2.micro
AllowedValues: [t2.micro]
Resources: # Mandatory Section, rest all are optional
MyBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: s3-sample-setup0
VersioningConfiguration:
Status: Enabled
Outputs:
# Data returned after stack creation
BucketName:
Value: !Ref MyBucket
Everything except Resources is optional. A CloudFormation template with just a Resources block is valid and will work. Everything else is structure that makes templates maintainable.
Sample template for S3 Bucket with Versioning
Let's write something real. An S3 bucket with versioning enabled.
AWSTemplateFormatVersion: "2010-09-17"
Description: Creates an S3 bucket with versioning enabled.
Resources:
myBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: s3-sample-setup0
VersioningConfiguration:
Status: Enabled
Save as template.yaml.
The Type field tells CloudFormation what needs to be created. myBucket is just the logical name inside the template. The Type: AWS::S3::Bucket is what matters.
Finding property syntax: You can checkout the syntax at CloudFormation resource reference. You don't memorize this - you look it up.
Deploying the Stack
Prepare template: Template is ready
Template source: Upload your
template.yamlStack name:
s3-bucket-drift-detectionSkip through the rest with defaults → Submit
Go to S3 and verify: your bucket exists, and in Properties → Bucket Versioning, it should show Enabled.
Drift Detection - This makes it worth it
We've created infrastructure through a template. Everything matches. A random day somebody goes into the console and manually changes something. Maybe they disable versioning on the bucket. The template now no longer reflects reality.
This is called drift, and it's how infrastructure silently degrades over time.
CloudFormation can detect it. Go to your stack → Stack actions → Detect drift. Wait a moment, then View drift results.
The drift results will show you exactly what changed:
Property: VersioningConfiguration.Status
Expected: Enabled
Actual: Suspended
Any drift means someone bypassed the IaC workflow - either they don't have access to the repo, or they were in a hurry, or they didn't know the process. Either way, you want to know immediately. So, you set up periodic drift detection and alert on it.
CloudFormation vs. Terraform
CloudFormation:
AWS-only
Native integration - no state file to manage, AWS handles it
Drift detection built in
Good choice if your work is 100% AWS and plans to stay that way
Terraform:
Multi-cloud (AWS, Azure, GCP, on-prem)
HCL syntax - separate from any cloud provider
State management is your responsibility (S3 + DynamoDB backend)





