AWS CloudFormation
CloudFormation is AWS's native IaC service. Unlike Terraform, state is fully managed by AWS — no state files to backup, no DynamoDB lock tables. Deep integration with every AWS service.
Architecture
graph TD
classDef blue fill:#3498db,stroke:#2980b9,color:#fff
classDef green fill:#2ecc71,stroke:#27ae60,color:#fff
classDef red fill:#e74c3c,stroke:#c0392b,color:#fff
classDef orange fill:#e67e22,stroke:#d35400,color:#fff
classDef purple fill:#9b59b6,stroke:#8e44ad,color:#fff
classDef teal fill:#1abc9c,stroke:#16a085,color:#fff
classDef dark fill:#2c3e50,stroke:#1a252f,color:#fff
classDef yellow fill:#f39c12,stroke:#d68910,color:#000
classDef k8s fill:#326ce5,stroke:#254ea8,color:#fff
classDef aws fill:#ff9900,stroke:#cc7a00,color:#000
TEMPLATE["CloudFormation Template (YAML or JSON)"]:::purple --> STACK["Stack unit of deployment"]:::green
STACK --> CFN_SVC["CloudFormation Service AWS-managed state"]:::purple
CFN_SVC --> RESOURCES["AWS Resources EC2, VPC, RDS, IAM..."]:::orange
CFN_SVC --> EVENTS["Stack Events audit trail of every operation"]:::blue
CFN_SVC --> OUTPUTS["Stack Outputs export values to other stacks"]:::teal
CHANGESET["Change Set preview before apply"]:::blue --> STACK
Stack Lifecycle
stateDiagram-v2
[*] --> CREATE_IN_PROGRESS: CreateStack
CREATE_IN_PROGRESS --> CREATE_COMPLETE: all resources created
CREATE_IN_PROGRESS --> CREATE_FAILED: resource creation failed
CREATE_FAILED --> DELETE_IN_PROGRESS: rollback triggered
CREATE_COMPLETE --> UPDATE_IN_PROGRESS: UpdateStack or ExecuteChangeSet
UPDATE_IN_PROGRESS --> UPDATE_COMPLETE: update succeeded
UPDATE_IN_PROGRESS --> UPDATE_ROLLBACK_IN_PROGRESS: update failed
UPDATE_ROLLBACK_IN_PROGRESS --> UPDATE_ROLLBACK_COMPLETE: rolled back
CREATE_COMPLETE --> DELETE_IN_PROGRESS: DeleteStack
DELETE_IN_PROGRESS --> [*]: DELETE_COMPLETE
Rollback behavior: If any resource fails during stack creation or update, CloudFormation automatically rolls back all changes in that operation. You see ROLLBACK_COMPLETE — all changes are undone.
Template Structure
AWSTemplateFormatVersion: "2010-09-09"
Description: "My application stack"
Parameters:
Environment:
Type: String
AllowedValues: [dev, staging, prod]
Default: dev
InstanceType:
Type: String
Default: t3.micro
Mappings:
EnvToAMI:
us-east-1:
prod: ami-0abcdef1234567890
dev: ami-0fedcba9876543210
Conditions:
IsProd: !Equals [!Ref Environment, prod]
Resources:
WebServer:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !FindInMap [EnvToAMI, !Ref AWS::Region, !Ref Environment]
Tags:
- Key: Environment
Value: !Ref Environment
# Conditional resource — only created in prod
BackupBucket:
Type: AWS::S3::Bucket
Condition: IsProd
Properties:
BucketName: !Sub "${AWS::StackName}-backup-${AWS::AccountId}"
Outputs:
WebServerPublicIP:
Value: !GetAtt WebServer.PublicIp
Export:
Name: !Sub "${AWS::StackName}-WebServerIP"
Intrinsic functions:
| Function | Use |
|---|---|
!Ref |
Reference parameter or resource logical ID |
!GetAtt |
Get attribute of a resource (!GetAtt MyBucket.Arn) |
!Sub |
String substitution (!Sub "arn:aws:s3:::${BucketName}/*") |
!FindInMap |
Look up value in Mappings |
!If |
Conditional value based on Condition |
!ImportValue |
Import output exported by another stack |
!Join |
Join strings (!Join [":", [a, b, c]] → a:b:c) |
!Select |
Select by index from a list |
Change Sets
Change sets are CloudFormation's equivalent of terraform plan — preview what will happen before executing.
sequenceDiagram
participant Dev as Developer/CI
participant CFN as CloudFormation
Dev->>CFN: CreateChangeSet (template + stack name)
CFN->>CFN: Compute diff: current stack vs new template
CFN-->>Dev: Change set created: list of + create, ~ update, - delete
Dev->>Dev: Review changes (no resources touched yet)
Dev->>CFN: ExecuteChangeSet
CFN->>CFN: Apply changes to resources
CFN-->>Dev: Stack UPDATE_COMPLETE
# Create a change set
aws cloudformation create-change-set \
--stack-name my-stack \
--template-body file://template.yaml \
--change-set-name my-changes \
--parameters ParameterKey=Environment,ParameterValue=prod
# Review the change set
aws cloudformation describe-change-set \
--stack-name my-stack \
--change-set-name my-changes
# Execute (actually apply)
aws cloudformation execute-change-set \
--stack-name my-stack \
--change-set-name my-changes
Replacement vs Update: Change sets show if a change requires replacement (resource destroyed and recreated) vs in-place update. A replacement of an RDS instance = data loss risk — the change set warns you.
Nested Stacks
Break large templates into smaller reusable components using AWS::CloudFormation::Stack.
graph TD
classDef blue fill:#3498db,stroke:#2980b9,color:#fff
classDef green fill:#2ecc71,stroke:#27ae60,color:#fff
classDef red fill:#e74c3c,stroke:#c0392b,color:#fff
classDef orange fill:#e67e22,stroke:#d35400,color:#fff
classDef purple fill:#9b59b6,stroke:#8e44ad,color:#fff
classDef teal fill:#1abc9c,stroke:#16a085,color:#fff
classDef dark fill:#2c3e50,stroke:#1a252f,color:#fff
classDef yellow fill:#f39c12,stroke:#d68910,color:#000
classDef k8s fill:#326ce5,stroke:#254ea8,color:#fff
classDef aws fill:#ff9900,stroke:#cc7a00,color:#000
ROOT["Root Stack my-app-prod"]:::blue --> VPC_STACK["Nested Stack: VPC AWS::CloudFormation::Stack"]:::purple
ROOT --> ECS_STACK["Nested Stack: ECS Cluster AWS::CloudFormation::Stack"]:::orange
ROOT --> RDS_STACK["Nested Stack: RDS AWS::CloudFormation::Stack"]:::purple
VPC_STACK --> VPC_OUTPUTS["Outputs: VpcId, SubnetIds"]:::teal
ECS_STACK --> ECS_INPUTS["Inputs: VpcId from VPC stack output"]:::teal
RDS_STACK --> RDS_INPUTS["Inputs: SubnetIds from VPC stack output"]:::teal
Resources:
VPCStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/my-bucket/vpc.yaml
Parameters:
CIDR: "10.0.0.0/16"
ECSStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/my-bucket/ecs.yaml
Parameters:
VpcId: !GetAtt VPCStack.Outputs.VpcId
SubnetIds: !GetAtt VPCStack.Outputs.PrivateSubnets
Cross-stack references (alternative to nested stacks):
# Stack A exports
Outputs:
VpcId:
Export:
Name: my-app-prod-VpcId
Value: !Ref VPC
# Stack B imports
Resources:
Subnet:
Properties:
VpcId: !ImportValue my-app-prod-VpcId
Cross-stack references create a hard dependency — you cannot delete Stack A while Stack B imports its value.
StackSets
Deploy a single CloudFormation template across multiple AWS accounts and/or regions from one operation.
graph TD
classDef blue fill:#3498db,stroke:#2980b9,color:#fff
classDef green fill:#2ecc71,stroke:#27ae60,color:#fff
classDef red fill:#e74c3c,stroke:#c0392b,color:#fff
classDef orange fill:#e67e22,stroke:#d35400,color:#fff
classDef purple fill:#9b59b6,stroke:#8e44ad,color:#fff
classDef teal fill:#1abc9c,stroke:#16a085,color:#fff
classDef dark fill:#2c3e50,stroke:#1a252f,color:#fff
classDef yellow fill:#f39c12,stroke:#d68910,color:#000
classDef k8s fill:#326ce5,stroke:#254ea8,color:#fff
classDef aws fill:#ff9900,stroke:#cc7a00,color:#000
ADMIN["Administrator Account StackSet definition"]:::blue --> OU["AWS Organization OU or list of target accounts"]:::blue
OU --> ACCOUNT_A["Account A (prod) us-east-1"]:::blue
OU --> ACCOUNT_B["Account B (staging) us-east-1, eu-west-1"]:::blue
OU --> ACCOUNT_C["Account C (dev) us-west-2"]:::blue
ADMIN -->|"deployment options"| CONCURRENT["Concurrent deployments (faster)"]:::green
ADMIN -->|"deployment options"| SERIAL["Serial deployments (safer for rollout)"]:::green
Use cases:
- Deploy security baselines (CloudTrail, GuardDuty, Config Rules) to all accounts
- Enforce IAM password policy across an organization
- Deploy shared networking (Transit Gateway attachments) to all accounts
aws cloudformation create-stack-set \
--stack-set-name security-baseline \
--template-body file://baseline.yaml \
--capabilities CAPABILITY_NAMED_IAM
aws cloudformation create-stack-instances \
--stack-set-name security-baseline \
--deployment-targets OrganizationalUnitIds=["ou-xxxx-yyyyyyy"] \
--regions us-east-1 eu-west-1
Drift Detection
# Detect drift on a stack
aws cloudformation detect-stack-drift --stack-name my-stack
# Check drift status
aws cloudformation describe-stack-drift-detection-status \
--stack-drift-detection-id <detection-id>
# See which resources drifted and how
aws cloudformation describe-stack-resource-drifts \
--stack-name my-stack \
--stack-resource-drift-status-filters MODIFIED DELETED
CFN drift detection compares the current live resource configuration against what the template defines. Drifted resources show MODIFIED or DELETED.
Fix: Remediate by either updating the template to match reality and re-deploying, or manually fixing the resource to match the template.
Custom Resources
When CloudFormation doesn't natively support a resource type, use Custom Resources backed by Lambda.
sequenceDiagram
participant CFN as CloudFormation
participant Lambda as Lambda Function
participant Resource as External Resource
CFN->>Lambda: Invoke with Create/Update/Delete event + properties
Lambda->>Resource: Create/update/delete the resource
Resource-->>Lambda: Success/failure
Lambda->>CFN: Send response to PreSignedUrl (SUCCESS or FAILED)
CFN->>CFN: Continue stack operation based on response
Resources:
# Lambda function that handles the custom logic
MyCustomResourceFunction:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Runtime: python3.12
Code:
ZipFile: |
import boto3, cfnresponse
def handler(event, context):
if event['RequestType'] == 'Create':
# do something with event['ResourceProperties']
cfnresponse.send(event, context, cfnresponse.SUCCESS, {'OutputKey': 'value'})
# The custom resource that triggers the Lambda
MyCustomResource:
Type: Custom::MyResource
Properties:
ServiceToken: !GetAtt MyCustomResourceFunction.Arn
MyParam: "some-value"
# Use the output
Outputs:
CustomOutput:
Value: !GetAtt MyCustomResource.OutputKey
Common use cases: DNS record creation in external providers, database schema initialization, Slack/PagerDuty notifications on stack events, resource types not yet in CloudFormation.