☁️ AWS CloudFormation
Terraform speaks every cloud. But when you live entirely inside AWS, its native IaC service earns a serious look: CloudFormation manages your state for you, rolls back automatically on failure, and integrates with every AWS feature the day it launches.
Week 13 · Wednesday: Infrastructure as Code · Lecture 3
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Explain what CloudFormation is and how it differs from Terraform
- Structure a template in YAML with parameters, resources, and outputs
- Describe what a stack is and how AWS manages its lifecycle and state
- Use core intrinsic functions like
!Ref,!GetAtt, and!Sub - Preview changes safely with change sets and catch manual edits with drift detection
- Decide when CloudFormation or Terraform is the better fit
Estimated Time: 65 minutes
Practice: Read a template, resolve intrinsic functions by hand, and reason about change sets.
In This Lesson
What Is CloudFormation?
AWS CloudFormation is Amazon's native Infrastructure as Code service. You describe the AWS resources you want in a declarative template — written in YAML or JSON — and CloudFormation provisions and manages them as a single unit called a stack. Launched in 2011, it was one of the first IaC services from a major cloud provider and supports essentially every AWS resource type.
The workflow mirrors what you already know from Terraform, with one big convenience: AWS manages the state for you. There's no state file to store, encrypt, or lock — the CloudFormation service tracks everything on its side.
YAML or JSON"] --> B["Create stack"] B --> C["CloudFormation service"] C --> D["Provision AWS resources"] D --> E["Stack created"] E --> F["Update or delete via new template"] F --> C
🍳 The recipe-and-kitchen analogy
- Templates are recipes: they list every ingredient and step to create a dish.
- Stacks are the finished dishes — a set of resources working together.
- Parameters are customer preferences that tweak the recipe, like "extra spicy."
- Change sets are the chef reviewing exactly what will change before touching the plate.
A standardized recipe gives consistent dishes every time — just as a template gives consistent infrastructure across environments.
CloudFormation vs. Terraform
Both are declarative IaC tools, but they make different trade-offs. Knowing the split helps you choose — and many teams use both.
| Feature | AWS CloudFormation | Terraform |
|---|---|---|
| Cloud support | AWS only | Multi-cloud (AWS, Azure, GCP, and more) |
| Language | YAML or JSON | HCL |
| State | Managed by AWS — nothing to store | You manage the state file |
| Change preview | Change sets | terraform plan |
| Rollback | Automatic on failure | Manual / custom automation |
| Drift detection | Built in | Via plan / refresh |
| New AWS features | Often available immediately | May lag until the provider updates |
💡 A simple rule of thumb
If your world is 100% AWS and you value automatic rollback plus zero state management, CloudFormation is a natural fit. If you span multiple clouds or prefer HCL's concise syntax and huge module ecosystem, Terraform wins. Neither choice is wrong — they solve the same problem from different angles.
Templates & Stacks
A template is a text file describing your resources. A stack is what you get when CloudFormation instantiates that template — a managed collection of real AWS resources you create, update, and delete together.
Anatomy of a template
Here's a minimal but complete YAML template that creates one EC2 instance:
AWSTemplateFormatVersion: '2010-09-09'
Description: A simple EC2 instance
Parameters:
InstanceType:
Description: EC2 instance size
Type: String
Default: t3.micro
AllowedValues: [t3.micro, t3.small, t3.medium]
Resources:
WebServer:
Type: AWS::EC2::Instance # the resource type
Properties:
InstanceType: !Ref InstanceType # pull the parameter value
ImageId: ami-0c55b159cbfafe1f0
Tags:
- Key: Name
Value: WebServer
Outputs:
InstanceId:
Description: ID of the created instance
Value: !Ref WebServer
Every template shares this shape: an optional Parameters section for inputs, a required Resources section describing what to build, and an optional Outputs section exposing useful values.
A stack groups resources into one unit
create, update, delete as one"] C --> F D --> F E --> F
Because a stack is one unit, deleting it cleanly removes every resource it created — no orphaned servers quietly running up your bill. And if a resource fails to create, CloudFormation automatically rolls the whole stack back to its last good state.
Managing stacks from the CLI
# Create a stack from a template file
aws cloudformation create-stack \
--stack-name my-web-app \
--template-body file://webapp.yaml \
--parameters ParameterKey=InstanceType,ParameterValue=t3.small
# Delete a stack (removes ALL its resources)
aws cloudformation delete-stack --stack-name my-web-app
# Read a stack's outputs
aws cloudformation describe-stacks \
--stack-name my-web-app \
--query "Stacks[0].Outputs"
Parameters & Outputs
Parameters let you pass custom values in at stack-creation time — the same template can build a tiny dev stack or a beefy production one. Outputs expose values after the stack is built.
Parameters with types and constraints
Parameters:
# String with a fixed set of allowed values
Environment:
Description: Deployment environment
Type: String
Default: Development
AllowedValues: [Development, Staging, Production]
# Number with min/max bounds
WebServerCapacity:
Description: Desired number of web servers
Type: Number
Default: 2
MinValue: 1
MaxValue: 10
# AWS-specific type: the console shows a dropdown of real VPCs
VpcId:
Description: VPC to deploy into
Type: AWS::EC2::VPC::Id
# Regex-constrained string
DBUsername:
Description: Database admin username
Type: String
MinLength: 1
MaxLength: 16
AllowedPattern: "[a-zA-Z][a-zA-Z0-9]*"
ConstraintDescription: must start with a letter, alphanumeric only
✅ AWS-specific parameter types are a gift
Types like AWS::EC2::VPC::Id or AWS::EC2::KeyPair::KeyName make the console render a dropdown of your real resources and validate the choice before the stack even starts. Fewer typos, fewer failed deploys.
Outputs — and cross-stack exports
Outputs:
WebsiteURL:
Description: Public URL of the site
Value: !Sub "http://${WebServer.PublicDnsName}"
BucketArn:
Description: ARN of the data bucket
Value: !GetAtt DataBucket.Arn
Export: # make it importable by other stacks
Name: !Sub "${AWS::StackName}-BucketArn"
The Export block publishes a value other stacks can pull in with !ImportValue — the CloudFormation way to wire separate stacks together.
Intrinsic Functions
Templates aren't just static lists — intrinsic functions add references and lightweight logic. In YAML they use the short !Name form. These four cover the vast majority of real templates.
| Function | What it does | Example |
|---|---|---|
!Ref | Reference a parameter or resource | !Ref InstanceType |
!GetAtt | Read an attribute of a resource | !GetAtt WebServer.PublicIp |
!Sub | Substitute variables into a string | !Sub "http://${WebServer.PublicDnsName}" |
!Join | Join a list with a delimiter | !Join ["-", [prod, app]] |
Resources:
DataBucket:
Type: AWS::S3::Bucket
Properties:
# !Sub injects the stack name and region into the bucket name
BucketName: !Sub "${AWS::StackName}-data-${AWS::Region}"
ReadPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: !Sub "${AWS::StackName}-read"
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:GetObject
# !GetAtt reads the bucket's ARN attribute
Resource: !GetAtt DataBucket.Arn
💡 Pseudo-parameters come free
Values like AWS::StackName, AWS::Region, and AWS::AccountId are provided automatically. Combined with !Sub, they let you build unique, region-aware names without hardcoding anything.
Conditional resources
The Conditions section plus !If let a single template behave differently per environment:
Conditions:
IsProduction: !Equals [!Ref Environment, Production]
Resources:
Database:
Type: AWS::RDS::DBInstance
Properties:
Engine: postgres
# Production gets Multi-AZ high availability; others don't
MultiAZ: !If [IsProduction, true, false]
BackupRetentionPeriod: !If [IsProduction, 30, 7]
Change Sets & Drift
Two features make CloudFormation safe to run against production: change sets let you preview updates, and drift detection catches sneaky manual edits.
Change sets — CloudFormation's "plan"
A change set shows exactly how a proposed template update would affect your running resources before you execute it — the CloudFormation equivalent of terraform plan.
# Create a change set to preview an update
aws cloudformation create-change-set \
--stack-name my-web-app \
--change-set-name add-cache \
--template-body file://webapp-v2.yaml
# Review it, then execute if it looks right
aws cloudformation execute-change-set \
--stack-name my-web-app \
--change-set-name add-cache
⚠️ Watch for "Replacement"
Some property changes can't be done in place — CloudFormation must replace the resource, deleting the old one and creating a new one. For a database or stateful resource, replacement can mean data loss. Always scan a change set for Replacement: True before executing against production.
Drift detection — catch the hand edits
If someone changes a resource in the console instead of through the template, the stack has drifted. CloudFormation can detect this so you can reconcile the template with reality.
# Kick off drift detection for a stack
aws cloudformation detect-stack-drift --stack-name my-web-app
# See which resources have drifted
aws cloudformation describe-stack-resource-drifts --stack-name my-web-app
📖 The lesson repeats for a reason
Whether it's Terraform state or a CloudFormation stack, the rule is the same: the template is the source of truth, and manual edits are drift. Change sets and drift detection are the guardrails that keep that promise honest.
Practice & Quiz
🏋️ Exercise 1: Resolve the functions by hand
Goal: The stack is named shop and deployed in us-east-1. What final string does this produce?
BucketName: !Sub "${AWS::StackName}-data-${AWS::Region}"
💡 Hint
!Sub replaces each ${...} with its value. AWS::StackName and AWS::Region are pseudo-parameters filled in automatically.
✅ Solution
It resolves to shop-data-us-east-1. The stack name and region are substituted in, producing a unique, region-aware bucket name with no hardcoded values.
🏋️ Exercise 2: Add a conditional output
Goal: Given a condition IsProduction, write an output BackupBucketName that returns !Ref BackupBucket in production and the literal string "none" otherwise.
✅ Solution
Outputs:
BackupBucketName:
Description: Backup bucket, if one was created
Value: !If [IsProduction, !Ref BackupBucket, "none"]
!If takes a condition and two values, returning the first when the condition is true. This is how one template adapts to many environments.
🎯 Quick Quiz
Question 1: What is a CloudFormation stack?
Question 2: Which CloudFormation feature is the equivalent of terraform plan?
Question 3: A key advantage of CloudFormation over Terraform for an all-AWS shop is:
Best Practices & Pitfalls
✅ Do
- Prefer YAML — it's more readable than JSON and supports comments
- Validate templates with
aws cloudformation validate-templatebefore deploying - Always create and review a change set for production updates
- Parameterize templates so one file serves dev, staging, and production
- Reference secrets via dynamic references to Secrets Manager, never inline
❌ Don't
- Hardcode passwords or keys in a template
- Execute an update without checking for
Replacement: Trueon stateful resources - Edit stack resources by hand in the console — that causes drift
- Skip stack policies on production — protect critical resources from accidental replacement
⚠️ Pull secrets in dynamically
Resources:
Database:
Type: AWS::RDS::DBInstance
Properties:
Engine: postgres
MasterUsername: admin
# Resolved from Secrets Manager at deploy time — never in the template
MasterUserPassword: '{{resolve:secretsmanager:prod/db:SecretString:password}}'
Dynamic references keep secrets out of your template and out of version control, the same discipline you applied to Terraform state.
Summary
🎉 Key Takeaways
- CloudFormation is AWS-native IaC using YAML/JSON templates deployed as stacks
- AWS manages state for you and rolls back automatically on failure
- Parameters customize a template per environment; outputs expose and export values
- Intrinsic functions —
!Ref,!GetAtt,!Sub,!If— add references and logic - Change sets preview updates safely; drift detection catches manual edits
- Choose CloudFormation for all-AWS estates, Terraform for multi-cloud — both keep the template as the source of truth
📚 Additional Resources
- AWS CloudFormation — product overview
- AWS CloudFormation — User Guide
- CloudFormation — intrinsic function reference
- HashiCorp — Terraform vs. CloudFormation
🚀 What's Next?
You can now provision infrastructure with both a provider-agnostic tool and an AWS-native one. Once infrastructure is running, you need to watch it: the next lesson is Application Monitoring — metrics, logs, dashboards, and alerts that tell you your systems are healthy.
🎉 IaC unlocked!
Terraform and CloudFormation in your toolkit means you can build, version, and rebuild entire environments on command.