CloudFormation#
AWS CloudFormation lets you define your entire AWS infrastructure as code — VPCs, databases, Lambda functions, IAM roles, and more — in a single declarative template. Instead of clicking through the console or running ad-hoc CLI commands, you describe what you want, and CloudFormation figures out how to create it, in the right order, handling dependencies automatically.
The core problem it solves is repeatability. Without IaC, recreating a production environment in a new region, or letting a teammate spin up an identical dev stack, is error-prone and slow. With CloudFormation, you version-control your infrastructure alongside your application code and deploy it consistently every time.
Template Structure#
A CloudFormation template is a YAML or JSON file with a set of top-level sections. Only Resources is mandatory; the rest are optional but frequently used 🔗.
AWSTemplateFormatVersion: "2010-09-09"
Description: "My application stack"
Parameters: # User inputs at deploy time
Mappings: # Static lookup tables
Conditions: # Boolean logic to toggle resources
Resources: # The actual AWS resources (required)
Outputs: # Values to expose after stack creationResources#
Every AWS resource you want CloudFormation to manage is declared under Resources. Each resource has a logical ID (used to reference it within the template), a Type, and Properties 🔗.
Resources:
MyBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: my-app-assets
VersioningConfiguration:
Status: EnabledThe logical ID (MyBucket) is how other parts of the template refer to this resource. The full list of supported resource types and their properties lives in the AWS resource and property types reference.
Parameters#
Parameters make templates reusable by accepting input at deploy time instead of hardcoding values 🔗.
Parameters:
EnvironmentName:
Type: String
AllowedValues: [dev, staging, prod]
Default: dev
InstanceType:
Type: String
Default: t3.microCommon parameter types include String, Number, AWS::EC2::KeyPair::KeyName, and AWS::SSM::Parameter::Value<String> (which fetches a value directly from SSM at deploy time). You can add constraints like MinLength, MaxLength, AllowedPattern, and AllowedValues to validate input before the stack even starts creating resources.
Pseudo-parameters are built-in values AWS provides automatically — no declaration needed. The most useful ones:
AWS::AccountId— the current AWS account IDAWS::Region— the region being deployed intoAWS::StackName— the name of the current stack
Mappings and Fn::FindInMap#
Mappings are static lookup tables embedded in the template — useful for region-specific AMI IDs, or environment-specific configurations 🔗.
Mappings:
RegionAMIMap:
us-east-1:
AMI: ami-0abcdef1234567890
eu-west-1:
AMI: ami-0fedcba9876543210You look up a value with Fn::FindInMap:
ImageId: !FindInMap [RegionAMIMap, !Ref AWS::Region, AMI]This is a common pattern for making templates work across multiple regions without hardcoding AMI IDs.
Conditions#
Conditions let you create or configure resources only when certain criteria are met — for example, only creating a larger instance type in production 🔗.
Conditions:
IsProd: !Equals [!Ref EnvironmentName, prod]
Resources:
MyInstance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !If [IsProd, m5.large, t3.micro]The key condition functions are Fn::Equals, Fn::If, Fn::And, Fn::Or, and Fn::Not. Conditions can be applied to entire resources (to conditionally create them) or to individual property values.
Intrinsic Functions#
Intrinsic functions are CloudFormation’s built-in helpers for dynamic values 🔗. The ones you’ll use constantly:
Ref— returns the primary identifier of a resource (e.g., a bucket name, a security group ID) or the value of a parameter.
BucketName: !Ref MyBucketFn::GetAtt— retrieves a specific attribute of a resource thatRefdoesn’t expose, like an S3 bucket’s ARN or a load balancer’s DNS name.
BucketArn: !GetAtt MyBucket.ArnFn::Sub— string substitution, the cleanest way to build ARNs or resource names dynamically.
FunctionArn: !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:my-function"Fn::Join— concatenates a list of values with a delimiter. Often replaced byFn::Subin modern templates, but still common.
!Join ["-", [!Ref EnvironmentName, "bucket"]]Fn::Select— picks one element from a list by index.
!Select [0, !GetAZs ""] # First availability zone in the regionOutputs and Cross-Stack References#
Outputs expose values from a stack — useful for displaying information after deployment (like an endpoint URL) or for sharing values between stacks 🔗.
Outputs:
BucketName:
Value: !Ref MyBucket
Export:
Name: !Sub "${AWS::StackName}-BucketName"Another stack in the same account and region can then import this value with Fn::ImportValue:
BucketName: !ImportValue MyBaseStack-BucketNameThis pattern — called cross-stack references — lets you split a large infrastructure into focused stacks (networking, data, application) while still sharing outputs between them. One important constraint: you cannot delete a stack that has exported values still being imported by another stack.
Stacks, Updates, and Change Sets#
A stack is a deployed instance of a template. You create, update, and delete stacks as a unit — CloudFormation manages the full lifecycle of every resource inside it 🔗.
When you update a template and redeploy, CloudFormation compares the new template against the deployed state and figures out the minimal set of changes needed. Before executing an update, you can preview the changes using a change set — it shows exactly which resources will be added, modified, or replaced, without actually making any changes 🔗. This is the recommended practice before any production update.
Nested Stacks and StackSets#
As infrastructure grows, a single template becomes unwieldy. Nested stacks let you compose a parent stack from reusable child stacks, each responsible for a discrete piece of infrastructure (e.g., a VPC stack, an RDS stack) 🔗.
Resources:
VpcStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/my-bucket/vpc.yaml
Parameters:
CidrBlock: "10.0.0.0/16"StackSets take this further — they let you deploy a single template across multiple AWS accounts and regions simultaneously, which is essential for organizations managing multi-account environments 🔗.
Stack Policies and Drift Detection#
A stack policy is a JSON document that controls which resources can be updated or replaced during a stack update — useful for protecting stateful resources like RDS instances from accidental replacement 🔗.
Drift detection identifies resources whose actual configuration has diverged from what the template declares — for example, someone manually changed a security group rule through the console. Running drift detection tells you exactly what’s out of sync 🔗.
DeletionPolicy and UpdateReplacePolicy#
By default, when a resource is removed from a template (or when a stack is deleted), CloudFormation deletes the underlying resource. DeletionPolicy overrides this behavior 🔗:
MyDatabase:
Type: AWS::RDS::DBInstance
DeletionPolicy: Snapshot # Takes a final snapshot before deleting
UpdateReplacePolicy: Retain # Retains the old resource if replaced during an updateThe three options for DeletionPolicy are Delete (default), Retain (leave the resource in place), and Snapshot (supported on RDS, ElastiCache, and a few others). UpdateReplacePolicy applies the same logic when CloudFormation must replace a resource during an update rather than modify it in place.
For production databases and S3 buckets containing critical data, always set DeletionPolicy: Retain or Snapshot. Accidentally deleting a production database by removing its resource block from a template is a painful lesson.
EC2 Helper Scripts: cfn-init, cfn-signal, cfn-hup#
When bootstrapping EC2 instances, CloudFormation provides three helper scripts that run on the instance itself 🔗:
cfn-initreads theAWS::CloudFormation::Initmetadata block on the resource and uses it to install packages, write files, and start services — a more structured alternative to rawUserDatascripts.cfn-signalsends a success or failure signal back to CloudFormation after the instance finishes bootstrapping. This is used with creation policies (CreationPolicy) to make CloudFormation wait for the instance to report ready before marking the resource asCREATE_COMPLETE. Without it, CloudFormation considers the EC2 instance created as soon as the API call succeeds — not when the application is actually running.cfn-hupis a daemon that polls for changes to the resource’s metadata and re-runscfn-initwhen it detects updates. This enables configuration changes to be pushed to running instances without replacing them.
MyInstance:
Type: AWS::EC2::Instance
CreationPolicy:
ResourceSignal:
Timeout: PT10M # Wait up to 10 minutes for the signal
Metadata:
AWS::CloudFormation::Init:
config:
packages:
yum:
httpd: []
services:
sysvinit:
httpd:
enabled: true
ensureRunning: true
Properties:
UserData:
Fn::Base64: !Sub |
#!/bin/bash
/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource MyInstance --region ${AWS::Region}
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource MyInstance --region ${AWS::Region}These scripts are especially relevant when you need EC2 instances to configure themselves at launch and need CloudFormation to know when that configuration is complete.