25.1
AWS CloudFormation
Infrastructure as code, stacks, change sets, StackSets and service roles with iam:PassRole.
20 min Reading + animations
Tip: tap or hover any dotted-underlined term to see what it means.
By the end of this lesson you will be able to
- Explain what infrastructure as code gives you (repeatable, reviewed, versioned, deletable environments, cost per stack, fast disaster recovery) and why CloudFormation is the AWS answer to “Infrastructure as Code”, “repeatable deployments” and “version control for infrastructure”.
- Read and write a YAML template:
Parameters(types,AllowedValues, SSM parameter types),Mappings,Conditions,Resources,Outputswith exports, intrinsic functions (Ref,Fn::GetAtt,Fn::Sub,Fn::Join,Fn::FindInMap,Fn::If,Fn::ImportValue) and pseudo parameters. - Follow a stack through its life: creation order, rollback and
ROLLBACK_COMPLETE, waiting for an instance withCreationPolicyandcfn-signal, updates with change sets, and deletion withDeletionPolicy. - Protect production:
DeletionPolicy(Retain,Snapshot,RetainExceptOnCreate),UpdateReplacePolicy, stack policies and termination protection. - Detect and fix drift, and say what drift detection cannot see.
- Use a service role correctly: the user needs CloudFormation permissions and
iam:PassRole, the role holds the resource permissions. - Choose between cross-stack references and nested stacks, deploy one template to many accounts and Regions with StackSets, and place custom resources, Infrastructure Composer, CDK, SAM, Terraform, CloudFormation Guard, the IaC generator and Git sync.
#Why: a console click is not a process
Lesson 1.3 introduced infrastructure as code (IaC): you describe the result you want in a file, and a tool makes the API calls. This lesson is about the AWS tool for that job, AWS CloudFormation, at the depth the exam and a real team need.
Picture building a VPC with four subnets, two route tables, a NAT gateway, three security groups, a load balancer, an Auto Scaling group and a database by clicking through the console. It works once. Then:
- Staging must look exactly like production. Someone repeats 150 clicks and forgets one security group rule. The bug that appears only in production is that missing rule.
- Nobody reviewed the change. A console click has no pull request, no second pair of eyes and no history beyond CloudTrail's record that “someone called
AuthorizeSecurityGroupIngress”. - You cannot delete it cleanly. Months later, nobody remembers which of the 40 resources belonged to the experiment, so they keep running and keep costing.
- A disaster needs it again, fast. Rebuilding by hand in another Region during an outage is slow and full of mistakes (lesson 23.1).
CloudFormation is a declarative way to describe AWS infrastructure. In a template you say “I want a security group, two EC2 instances using it, an S3 bucket and a load balancer in front of the instances”. CloudFormation works out the order, makes the calls with the exact configuration you wrote, and tracks everything it made as one unit, a stack. Almost every AWS resource type is supported, and new services usually get CloudFormation support at or soon after launch.
What that buys you:
| Benefit | What it means in practice |
|---|---|
| No manual resources | Everything in the stack came from the template, so the template is the documentation. |
| Code review | Infrastructure changes go through Git and pull requests like application code (“version control for infrastructure”). |
| Repeatable | The same template builds dev, staging and prod, in any Region or account, with different parameters. |
| Delete in one go | Deleting the stack deletes everything it created, in the right order. |
| Cost per stack | CloudFormation tags supported resources with aws:cloudformation:stack-name; activated as a cost allocation tag, it shows what each stack costs (lesson 25.4). |
| Savings on dev | A schedule can delete the dev stack at 5 PM and recreate it at 8 AM, safely, because the template rebuilds it identically. |
| Disaster recovery | The recovery Region needs only the templates (and data copies) to rebuild the environment. |
| No reinventing | AWS and the community publish sample templates for common designs (a WordPress site, a VPC with public and private subnets). |
CloudFormation itself is free for AWS resource types: you pay for the resources it creates. Third-party and private resource types and hooks have a small per-operation charge.
#Templates: what you write
#Template anatomy
A template is a JSON or YAML file. YAML is shorter and allows comments, so most teams use it. Here is a complete small template for the wallet's file-upload service, with every common section:
AWSTemplateFormatVersion: '2010-09-09' # optional; the only valid value
Description: Upload bucket and web security group for the wallet
Parameters:
Env:
Type: String
AllowedValues: [dev, staging, prod] # anything else is rejected before creation
Default: dev
VpcId:
Type: AWS::EC2::VPC::Id # AWS-specific type: must be a real VPC in this Region
LatestAmi:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
Mappings:
EnvSize:
dev: { InstanceType: t4g.micro }
staging: { InstanceType: t4g.small }
prod: { InstanceType: m7g.large }
Conditions:
IsProd: !Equals [!Ref Env, prod]
Resources: # the only required section
UploadBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain # keep customer files if the stack is deleted
UpdateReplacePolicy: Retain
Properties:
BucketName: !Sub 'wallet-uploads-${Env}-${AWS::AccountId}-example'
VersioningConfiguration:
Status: !If [IsProd, Enabled, Suspended]
WebSg:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub 'Web tier ${Env}'
VpcId: !Ref VpcId
SecurityGroupIngress:
- { IpProtocol: tcp, FromPort: 443, ToPort: 443, CidrIp: 0.0.0.0/0 }
WebServer:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmi
InstanceType: !FindInMap [EnvSize, !Ref Env, InstanceType]
SecurityGroupIds: [!GetAtt WebSg.GroupId] # implicit dependency: WebSg first
Outputs:
BucketArn:
Value: !GetAtt UploadBucket.Arn
Export:
Name: !Sub '${AWS::StackName}-BucketArn' # other stacks can import this
| Section | Required? | What it holds |
|---|---|---|
AWSTemplateFormatVersion | No | The format version; 2010-09-09 is the only value. |
Description | No | Free text, up to 1,024 bytes. |
Metadata | No | Extra data, for example the interface layout of the console, or cfn-init configuration. |
Parameters | No | Inputs given at create or update time (up to 200). |
Rules | No | Checks across parameters before anything is created (“prod needs a large instance type”). |
Mappings | No | Fixed lookup tables (per Region, per environment), read with Fn::FindInMap. |
Conditions | No | Booleans that decide whether a resource, property or output exists. |
Transform | No | Macros that rewrite the template first, such as AWS::Serverless-2016-10-31 for SAM. |
Resources | Yes | The resources to create (up to 500 per template), each with a logical ID, a Type and Properties. |
Outputs | No | Values to show or export for other stacks (up to 200). |
A template body can be passed inline up to 51,200 bytes; a larger one (up to 1 MB) must be uploaded to S3 first, which aws cloudformation deploy --s3-bucket or package does for you.
#Parameters, mappings and conditions
- Parameters make one template serve many environments. Types include
String,Number,CommaDelimitedList, and AWS-specific types such asAWS::EC2::VPC::IdorAWS::EC2::KeyPair::KeyName, which the console shows as drop-down lists and CloudFormation validates.AllowedValues,AllowedPattern,MinValue/MaxValuereject bad input before anything is built.NoEcho: truehides a value in the console and API output, but a real secret belongs in Secrets Manager and a dynamic reference (lesson 21.3). - SSM parameter types read a value from Parameter Store at deploy time.
AWS::SSM::Parameter::Valueof the typeAWS::EC2::Image::Idwith the public parameter for the newest Amazon Linux AMI means the template never hard-codes an AMI ID, which differs per Region anyway. The value is resolved on each create or update, so the AMI changes only when you update the stack. - Mappings are fixed tables: an AMI per Region, an instance size per environment. Use them for values you know in advance; use parameters for values the person deploying chooses.
- Conditions create resources only in some environments: a second NAT gateway, versioning or a read replica only when
Envisprod. A resource withCondition: IsProdsimply does not exist in dev.
#Intrinsic functions and pseudo parameters
| Function (short YAML form) | Returns | Example |
|---|---|---|
!Ref | A parameter's value, or a resource's main identifier (instance ID, bucket name, security group ID…) | !Ref VpcId |
!GetAtt | Another attribute of a resource | !GetAtt UploadBucket.Arn, !GetAtt Db.Endpoint.Address |
!Sub | A string with ${...} placeholders filled in | !Sub 'arn:aws:s3:::${UploadBucket}/*' |
!Join | Values joined with a delimiter | !Join [',', [a, b, c]] |
!FindInMap | A value from Mappings | !FindInMap [EnvSize, !Ref Env, InstanceType] |
!If | One of two values, by condition | !If [IsProd, Enabled, Suspended] |
!ImportValue | An export of another stack | !ImportValue wallet-network-VpcId |
!Equals, !And, !Or, !Not | Booleans for Conditions | !Equals [!Ref Env, prod] |
!Select, !Split, !GetAZs, !Cidr, !Base64 | Lists, AZ names, subnet ranges, user data | !Select [0, !GetAZs ''] |
Pseudo parameters are predefined by CloudFormation: AWS::Region, AWS::AccountId, AWS::StackName, AWS::StackId, AWS::Partition, AWS::URLSuffix, and AWS::NoValue (removes a property, often inside !If). They let one template run unchanged in any account and Region.
#Dependencies and creation order
You never write the order. CloudFormation builds a dependency graph from the template:
- Implicit dependencies: a resource that uses
!Refor!GetAtton another resource depends on it.WebServerusesWebSg.GroupId, so the security group is created first. - Explicit dependencies:
DependsOn: [Name]when there is no reference but order still matters. The classic case: an Elastic IP or a NAT gateway in a VPC must wait for the internet gateway attachment, which no property references. - Parallel creation: resources with no dependency between them are created at the same time, which is why a 60-resource stack takes minutes, not hours. Deletion walks the same graph in reverse.
From template to stack
The wallet team’s template declares a security group, an EC2 instance that uses it and an S3 bucket with DeletionPolicy Retain. Watch the order CloudFormation chooses, and what happens when something fails.
Scenario
CloudFormation builds a dependency graph and creates independent resources in parallel.
The engineer runs create-stack with wallet.yaml. Nobody writes the order of steps: the template only says what should exist.
#The life of a stack
#Stack states
| Status | Meaning |
|---|---|
CREATE_IN_PROGRESS → CREATE_COMPLETE | Creation running, then finished successfully. |
CREATE_FAILED | A resource failed; with rollback disabled the stack stays here for debugging. |
ROLLBACK_IN_PROGRESS → ROLLBACK_COMPLETE | Creation failed and everything created so far was deleted. The stack can only be deleted. |
UPDATE_IN_PROGRESS → UPDATE_COMPLETE_CLEANUP_IN_PROGRESS → UPDATE_COMPLETE | An update, then removal of replaced old resources. |
UPDATE_ROLLBACK_IN_PROGRESS → UPDATE_ROLLBACK_COMPLETE | An update failed and the stack returned to its last working state. It is usable and can be updated again. |
UPDATE_ROLLBACK_FAILED | Even the rollback failed (often because someone deleted a resource by hand). Fix the cause, then continue update rollback. |
DELETE_IN_PROGRESS → DELETE_COMPLETE / DELETE_FAILED | Deletion; it fails, for example, on a non-empty S3 bucket. Retry, retaining the stuck resource. |
#When creation fails
CloudFormation treats a stack as all or nothing. If any resource fails during creation, by default it rolls back: it deletes every resource it already created, and the stack ends in ROLLBACK_COMPLETE. That stack cannot be updated or fixed; it is a record of the failure. Read the Events tab for the first CREATE_FAILED reason, delete the stack, fix the template and create it again.
When the reason is not obvious (a bootstrap script that dies on the instance), rollback deletes exactly the evidence you need. For debugging:
--disable-rollback(or--on-failure DO_NOTHING) keeps the resources and leaves the stack inCREATE_FAILED. Inspect the instance and its logs, then fix and retry with an update, or roll back explicitly withrollback-stack.--retain-except-on-createkeeps resources whoseDeletionPolicysaysRetain, except the ones created by this very operation.
On an update failure, rollback returns the stack to the previous template and the stack ends in UPDATE_ROLLBACK_COMPLETE, a working state. Only a failed create leaves the one-way ROLLBACK_COMPLETE.
#Waiting for an instance to be ready: CreationPolicy and cfn-signal
An EC2 instance is “created” as soon as EC2 says it is running, but your application may need ten minutes of installation after that. If a load balancer or a test depends on the instance, CloudFormation should wait for the software, not just the server. That is the job of a CreationPolicy:
WebServer:
Type: AWS::EC2::Instance
CreationPolicy:
ResourceSignal: { Count: 1, Timeout: PT15M } # wait up to 15 minutes for 1 success signal
Metadata:
AWS::CloudFormation::Init: # what cfn-init installs
config:
packages: { dnf: { nginx: [] } }
services: { systemd: { nginx: { enabled: true, ensureRunning: true } } }
Properties:
UserData:
Fn::Base64: !Sub |
#!/bin/bash
/opt/aws/bin/cfn-init -s ${AWS::StackName} -r WebServer --region ${AWS::Region}
/opt/aws/bin/cfn-signal -e $? -s ${AWS::StackName} -r WebServer --region ${AWS::Region}
The CloudFormation helper scripts (package aws-cfn-bootstrap, preinstalled on Amazon Linux): cfn-init reads the AWS::CloudFormation::Init metadata and installs packages, writes files and starts services; cfn-signal reports success (exit code 0) or failure back to CloudFormation; cfn-hup watches the metadata and re-runs actions when a stack update changes it; cfn-get-metadata prints it. If no success signal arrives before the timeout (up to 12 hours), the resource fails and the stack rolls back. The same policy works on an Auto Scaling group (“wait until 3 instances signal”). The instance needs a network path to the CloudFormation endpoint to send the signal: a NAT gateway or an interface VPC endpoint in a private subnet.
#Changing a stack safely
#Three kinds of update
When you change a property, CloudFormation updates the existing resource in one of three ways. The resource reference lists which one each property needs:
| Update behaviour | What happens | Example |
|---|---|---|
| No interruption | Changed in place; the resource keeps working and keeps its ID | A security group rule, tags, an S3 lifecycle rule |
| Some interruption | Changed in place, with a short outage | An EC2 instance type (the instance is stopped and started) |
| Replacement | A new resource is created, references are switched to it, then the old one is deleted | An EC2 ImageId, an RDS DBInstanceIdentifier, an S3 BucketName |
Replacement is the dangerous one: the new resource has a new physical ID, a new IP address, and for a database, no data unless a snapshot is restored. A resource with a fixed name cannot be replaced at all while the old one exists, because the name is taken; leave names to CloudFormation unless you need them.
#Change sets: preview before you apply
A change set is a diff that CloudFormation computes without changing anything: for each resource, the action (Add, Modify, Remove, Import) and, for a modify, whether it needs replacement (True, False or Conditional). You review it, then execute it or delete it. Updating a stack directly skips the preview; many teams allow production changes only through change sets.
Action LogicalId Type Replacement
Modify WebSg AWS::EC2::SecurityGroup False
Modify WebServer AWS::EC2::Instance True <- new instance, new IP
Add AlarmHigh AWS::CloudWatch::Alarm -
A change set shows what CloudFormation intends to do; it cannot promise success (a quota or a name clash can still fail the update), although since November 2025 change sets run pre-deployment validation that catches many such errors early.
#Protecting what matters
| Control | Protects against | How |
|---|---|---|
DeletionPolicy | Losing a resource when it is removed from the template or the stack is deleted | Delete (default), Retain (keep it, any resource type), Snapshot (take a final snapshot, then delete), RetainExceptOnCreate (keep it, unless it was created by the operation that is rolling back) |
UpdateReplacePolicy | Losing the old resource when an update replaces it | Same values: Delete, Retain, Snapshot |
| Stack policy | Updates to critical resources through CloudFormation | A JSON document on the stack: once set, every resource is protected unless allowed; allow Update:* generally and deny Update:Replace and Update:Delete on the production database |
| Termination protection | Someone deleting the whole stack | update-termination-protection --enable-termination-protection; nested stacks inherit it from the root stack |
Snapshot is supported only by resources that have snapshots: AWS::EC2::Volume, AWS::RDS::DBInstance, AWS::RDS::DBCluster, AWS::ElastiCache::CacheCluster, AWS::ElastiCache::ReplicationGroup, AWS::Redshift::Cluster, AWS::Neptune::DBCluster and AWS::DocDB::DBCluster. For AWS::RDS::DBCluster, and for an AWS::RDS::DBInstance that is not part of a cluster, the default is already Snapshot. An S3 bucket cannot be snapshotted; use Retain. A bucket with objects in it cannot be deleted at all, so Delete on a non-empty bucket ends in DELETE_FAILED.
A stack policy example that lets the team change anything except replacing or deleting the database:
{
"Statement": [
{ "Effect": "Allow", "Principal": "*", "Action": "Update:*", "Resource": "*" },
{ "Effect": "Deny", "Principal": "*", "Action": ["Update:Replace", "Update:Delete"],
"Resource": "LogicalResourceId/WalletDb" }
]
}
A stack policy cannot be removed once set, only replaced; for a deliberate change you pass a temporary overriding policy with that one update. It controls updates made through CloudFormation only: it does not stop someone from changing the database in the RDS console (that is drift), and it does not stop stack deletion (that is termination protection plus DeletionPolicy).
Updating a stack with change sets
A change set is a preview: CloudFormation computes what it would do and changes nothing until you execute it. The Replacement column is the one to read.
Scenario
Some property changes are applied to the existing resource while it keeps running.
The engineer adds a CostCenter tag to the web server and creates a change set instead of updating directly.
#Drift: when someone clicks anyway
CloudFormation knows only what it did. If someone edits a stack resource in the console, the stack still says UPDATE_COMPLETE, but reality no longer matches the template. That difference is drift, and drift detection finds it: CloudFormation reads the live configuration of every supported resource and compares it with the template and parameters.
| Resource drift status | Meaning |
|---|---|
IN_SYNC | Matches the template |
MODIFIED | A property differs; the details show expected vs actual (NOT_EQUAL, ADD, REMOVE) |
DELETED | The resource was deleted outside CloudFormation |
NOT_CHECKED | This resource type does not support drift detection |
What drift detection does not see:
- Resources outside the stack. An instance someone launched by hand is invisible to every stack; AWS Config (lesson 19.7) is the tool that watches the whole account.
- Properties the template does not set. Only explicitly set properties are compared, not defaults. If you want a property watched, set it, even to its default value.
- Values a service never returns, such as passwords, and things CloudFormation cannot map back, such as a Lambda function's code.
- Nested stacks are not checked by a drift detection on the parent; run it on each nested stack.
- It is not continuous: it runs when you start it (or on a schedule you build, for example with an EventBridge rule and the AWS Config managed rule
cloudformation-stack-drift-detection-check).
How to fix drift:
- Put the resource back by hand to match the template, or
- Change the template to match reality if the manual change was right, and update the stack, or
- Since November 2025, create a drift-aware change set (
--deployment-mode REVERT_DRIFT): CloudFormation compares template, last deployment and live state, and the update brings the drifted resources back to the template. - For a resource that was created outside CloudFormation and should be managed by it, use resource import: add it to the template with a
DeletionPolicy, and create a change set of typeIMPORT.
Drift: the template vs reality
A junior admin changes stack resources in the console. CloudFormation does not notice until someone runs drift detection, and even then it sees only what it manages.
Scenario
A manual rule on a stack resource shows up as MODIFIED with an ADD difference.
At 2 AM, during an incident, the junior admin adds SSH from 0.0.0.0/0 to the web security group in the console “for ten minutes”, and forgets it.
#The service role and iam:PassRole
By default CloudFormation acts with the permissions of whoever starts the operation: it builds a temporary session from your credentials. To create an EC2 instance through a stack, you need ec2:RunInstances yourself. In a regulated company that is exactly what the security team does not want: developers should deploy approved stacks, not launch anything they like.
A service role solves it. It is an IAM role that CloudFormation assumes (its trust policy names cloudformation.amazonaws.com) and that holds the resource permissions. The user only needs permission to work with stacks and to hand that role to CloudFormation:
| Who | Needs | Example |
|---|---|---|
| The user | cloudformation:CreateStack, UpdateStack, DeleteStack, CreateChangeSet… (often scoped to stack names), and iam:PassRole on the role | cloudformation:* on stack/app-*, iam:PassRole on role/cfn-app-deployer |
| The service role | The permissions for the resources in the template | ec2:* for instances and security groups, s3:*Bucket for buckets |
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow",
"Action": ["cloudformation:CreateStack", "cloudformation:UpdateStack", "cloudformation:DeleteStack",
"cloudformation:CreateChangeSet", "cloudformation:ExecuteChangeSet", "cloudformation:Describe*"],
"Resource": "arn:aws:cloudformation:ap-southeast-1:111122223333:stack/app-*/*" },
{ "Effect": "Allow", "Action": "iam:PassRole",
"Resource": "arn:aws:iam::111122223333:role/cfn-app-deployer",
"Condition": { "StringEquals": { "iam:PassedToService": "cloudformation.amazonaws.com" } } }
]
}
Why iam:PassRole matters (lesson 2.4): without that check, a user allowed to create stacks could name an administrator role as the service role and create anything. Limiting iam:PassRole to one deployment role is what makes the design least privilege.
Details worth knowing:
- Once a stack has a service role, CloudFormation uses it for every operation on that stack, and it cannot be removed (only changed, by someone allowed to pass the new role).
- Anyone with permission to update or delete that stack uses its role, even without
iam:PassRole. So keep the role narrow, and restrictcloudformation:UpdateStackon sensitive stacks. - Without a service role, a user without
ec2:RunInstancescan still callCreateStack; the stack starts, the instance fails withAccessDenied, and the stack rolls back.
Deploying through a service role
A bank developer may deploy stacks but may not launch EC2 instances herself. The service role cfn-app-deployer holds the EC2 permissions; she holds only stack permissions and iam:PassRole.
Scenario
The role does the work; the developer only hands it over.
She creates the payments API stack and names the role cfn-app-deployer with --role-arn.
Can this developer deploy?
The stack app-payments-api creates one EC2 instance. Switch the permissions and the role, then press Deploy.
Developer’s permissions
Stack and service role
Set the switches, then press Deploy.
#Splitting and sharing stacks
One giant stack becomes slow, risky and shared by teams who should not touch each other's parts. Two tools split it, and the exam asks you to tell them apart:
Cross-stack references (exports)Nested stacks
- Stack A exports an output (
Export: Name: wallet-network-VpcId); stack B reads it with!ImportValue wallet-network-VpcId. - The stacks have separate lifecycles, often separate owners: the network team updates the network stack, the app teams deploy their own stacks on top.
- Export names are unique per account and Region; imports work only within the same Region and account.
- While any stack imports an export, the exporting stack cannot be deleted and the exported value cannot be changed.
- Use for: sharing a VPC, subnets or a security group with many independent stacks.
- A parent (root) stack contains
AWS::CloudFormation::Stackresources that point to child templates in S3. - The children are created, updated and deleted with the parent, as one unit; values pass through parameters and
!GetAtt Child.Outputs.Name. - Change the child template once and every parent that uses it gets the change at its next update.
- Update and delete the root, not the children directly.
- Use for: reusing a component (a standard load balancer, a hardened bucket) inside many stacks, and getting around the 500-resource limit.
Rule of thumb: reuse a component → nested stacks; share outputs between stacks that live separately → exports and ImportValue. Since February 2025, stack refactoring can also move existing resources from one stack to another (or rename logical IDs) without recreating them, which helps when splitting a monolith stack.
Sharing values between stacks
Exports connect stacks that live separately; nested stacks are pieces of one stack. The difference shows when you try to delete something.
Scenario
One team exports, many teams import, each on its own schedule.
Exports
wallet-network-VpcId
vpc-0abc1234
wallet-network-PrivateSubnets
subnet-0a…, subnet-0b…
The network team’s stack has an output with Export Name wallet-network-VpcId. Export names must be unique in the account and Region.
#StackSets: one template, many accounts and Regions
A stack lives in one account and one Region. An organization with 40 accounts that wants the same guardrails everywhere (a CloudTrail trail, a Config recorder, an IAM role for the security team, a budget alarm) would need 40 × Regions stacks. CloudFormation StackSets creates, updates and deletes them all from one place:
- You create the StackSet in an administrator account; it is a Regional resource there. You choose target accounts (or Organizations OUs) and Regions. Each account and Region pair is a stack instance, backed by an ordinary stack in that account.
- Updating the StackSet updates all its stack instances; you cannot give one stack instance a different template (only different parameter overrides).
- Self-managed permissions: you create the IAM roles yourself,
AWSCloudFormationStackSetAdministrationRolein the administrator account andAWSCloudFormationStackSetExecutionRolein each target account. Works with any accounts, even outside an organization. - Service-managed permissions: with trusted access to AWS Organizations, StackSets creates the roles for you. You target OUs, the administrator is the management account or a delegated administrator, and automatic deployment creates the stack in every account that joins the OU later (and deletes or retains it when an account leaves). Stacks are not deployed to the management account itself.
- Deployment options: maximum concurrent accounts (a number or percentage), failure tolerance per Region (how many failures before the operation stops), Region concurrency (sequential, the default, or parallel) and the Region order.
One template, every account
An ISP rolls the same guardrails stack to every account in its workloads OU, in Singapore and Mumbai, from one StackSets administrator account.
Scenario
Service-managed permissions: target the OU, StackSets creates the roles and the stacks.
The security team creates the StackSet with service-managed permissions. Organizations trusted access lets StackSets create the roles it needs in every member account.
#Custom resources: when there is no resource type
Sometimes a stack must do something CloudFormation has no resource type for: look up a value in an on-premises system, empty an S3 bucket before deleting it, register the new server with a third-party monitoring tool. A custom resource (Type: Custom::EmptyBucket) points to a Lambda function (or an SNS topic) with ServiceToken. On every create, update and delete of that resource, CloudFormation invokes it with the request type and the properties, plus a pre-signed S3 URL; your code does the work and must send a SUCCESS or FAILED response to that URL. Data it returns is readable with !GetAtt. If the function crashes without answering, the stack waits for the ServiceTimeout (default and maximum one hour, 2024 property), then fails. For reusable extensions there are also registry resource types (third-party and your own, such as a Datadog monitor), which are typed and validated.
#Tools around CloudFormation
| Tool | What it is | Relation to CloudFormation |
|---|---|---|
| AWS Infrastructure Composer | A visual designer: drag resources onto a canvas, connect them, and it writes the template; it can also draw a diagram of an existing template (the WordPress stack on the slides) | Renamed from Application Composer in October 2024; in the CloudFormation console it replaced the old CloudFormation Designer. Status (Nov 2026): closed to new customers since 29 October 2026, the standalone console shuts down on 7 December 2026; the same visual editor stays in the AWS Toolkit for Visual Studio Code, and the CloudFormation console gets its own built-in visual editor |
| AWS CDK | Infrastructure in TypeScript, Python, Java, C# or Go (lesson 1.3) | cdk synth generates a CloudFormation template; cdk deploy deploys it as a stack |
| AWS SAM | Short syntax for serverless resources (lesson 15.1) | A template with Transform: AWS::Serverless-2016-10-31; CloudFormation expands AWS::Serverless::Function into the full resources |
| Elastic Beanstalk | Platform for web apps (lesson 9.4) | Each environment is a CloudFormation stack Beanstalk writes for you |
| Terraform | HashiCorp's multi-cloud IaC tool, written in HCL, with its own state file | Not CloudFormation: calls the AWS APIs directly. The exam's AWS-native answer is CloudFormation |
| CloudFormation Guard | Policy as code: rules such as “every bucket must have encryption”, checked with cfn-guard validate in a pipeline | Also used for AWS Config custom policy rules (lesson 19.7) and CloudFormation hooks, which block non-compliant resources before deployment (lesson 20.3) |
| IaC generator (February 2024) | Scans resources that already exist in the account and generates a template from them | Import them into a stack, or turn the template into a CDK app (cdk migrate) |
| Git sync (November 2023) | Links a stack to a template and a deployment file in a GitHub, GitLab or Bitbucket repository | A push to the branch updates the stack |
#Which answer?
| The question says | Answer | Near miss and why it loses |
|---|---|---|
| Infrastructure as code, repeatable environments | CloudFormation | Elastic Beanstalk deploys an app platform, not arbitrary infrastructure; OpsWorks is retired |
| IaC in TypeScript or Python | AWS CDK (synthesizes CloudFormation) | SAM is YAML for serverless |
| Preview changes, which resources will be replaced | Change set | Drift detection compares live vs template |
| Someone changed resources manually | Drift detection | Change sets compare two templates; CloudTrail tells who, not how far from the template |
| Keep the database when the stack is deleted | DeletionPolicy: Retain or Snapshot | Termination protection stops the whole delete; a stack policy covers updates only |
| Prevent updates to the production database through the stack | Stack policy | IAM on the user does not distinguish logical resources in the stack |
| Prevent accidental stack deletion | Termination protection | DeletionPolicy keeps resources but the stack is still deleted |
| Developers deploy but cannot create EC2 directly | Service role + iam:PassRole | Giving developers ec2:* breaks least privilege |
| Same stack in many accounts and Regions, new accounts automatically | StackSets, service-managed, automatic deployment | Nested stacks stay in one account and Region |
| Share a VPC ID with many independent stacks | Outputs with Export + Fn::ImportValue | Nested stacks bind the lifecycles together |
| Reuse a standard component in many templates | Nested stacks (or modules) | Copy-paste drifts apart |
| Wait until the instance's software is installed | CreationPolicy + cfn-signal (with cfn-init) | DependsOn only waits for the resource to be created |
| Do something CloudFormation has no resource for | Custom resource (Lambda-backed) | A script outside the stack is not part of its lifecycle |
| Template from existing resources | IaC generator | Drift detection only compares resources already in a stack |
#What goes wrong
- Stack stuck in
ROLLBACK_COMPLETE: the first creation failed. It cannot be updated; read the firstCREATE_FAILEDevent, delete the stack, fix, create again. Use--disable-rollbackto keep the resources for debugging next time. UPDATE_ROLLBACK_FAILED: the rollback could not restore something, often because a resource was deleted or changed by hand. Fix it manually, then continue the update rollback (skipping resources only as a last resort).DELETE_FAILEDon an S3 bucket: the bucket is not empty. Empty it (or a custom resource does it), or retry the delete retaining that resource.- An update replaced the database: someone changed a property that needs replacement. Always read the
Replacementcolumn of the change set; protect the database with a stack policy,DeletionPolicy: SnapshotandUpdateReplacePolicy: Snapshot. - “Export … cannot be deleted as it is in use by …”: another stack imports it. Remove the import from the consumers first.
- Creation hangs, then fails after the
CreationPolicytimeout:cfn-signalnever ran (script error, helper scripts missing) or could not reach CloudFormation from a private subnet without NAT or an endpoint. AccessDeniedoniam:PassRole: the user may create stacks but not pass that role. Allowiam:PassRoleon the specific role ARN.AccessDeniedfrom EC2 during creation, then rollback: no service role was given and the user lacks the resource permission, or the service role lacks it.- “Role … is invalid or cannot be assumed”: the role's trust policy does not trust
cloudformation.amazonaws.com. - Drift reported
IN_SYNCbut the system is wrong: the changed property was never set in the template, the resource is outside the stack, or it is in a nested stack you did not check. - A StackSet operation stops halfway: failures in a Region exceeded the failure tolerance; remaining Regions were cancelled. Fix the failing accounts and run the operation again; stack instances show
OUTDATEDuntil then. - A custom resource hangs for an hour: the Lambda function never sent its response (a crash, or no network path to S3). Always respond in a
finallyblock, and set a shorterServiceTimeout.
#In system design
- Layered stacks: network (VPC, subnets, endpoints) → shared services (security groups, KMS keys) → applications. Lower layers export, upper layers import, and each layer has its own owner and pipeline.
- Pipelines: a template change goes through Git → a pipeline runs
cfn-lintand Guard → creates a change set → a human approves → executes. Production stacks have termination protection, a stack policy and a service role. - Multi-account landing zones: Control Tower and StackSets roll out guardrails to every account; account-specific stacks deploy through pipelines with service roles.
- Disaster recovery: the same templates, deployed to the recovery Region by a pipeline, with AMI IDs and parameters resolved from Parameter Store (lesson 23.1).
- Cost control: dev stacks deleted every evening and recreated every morning (Sunday to Thursday in Dhaka), and cost reports grouped by the stack-name tag.
#Try it yourself
AWS CLI v2 in ap-southeast-1. Save the template from “Template anatomy” as wallet.yaml. The resources cost a little while they run; delete the stack at the end (the bucket is retained by design, so delete it by hand).
# 1. Check the syntax and see the parameters
aws cloudformation validate-template --template-body file://wallet.yaml \
--query 'Parameters[].ParameterKey'
# [ "Env", "VpcId", "LatestAmi" ]
# 2. Create (or update) the stack in one command; deploy builds and executes a change set
aws cloudformation deploy --stack-name wallet-dev --template-file wallet.yaml \
--parameter-overrides Env=dev VpcId=vpc-0abc1234def567890 \
--tags Project=wallet
# Waiting for changeset to be created..
# Waiting for stack create/update to complete
# Successfully created/updated stack - wallet-dev
aws cloudformation describe-stacks --stack-name wallet-dev \
--query 'Stacks[0].[StackStatus,Outputs[0].ExportName]'
# [ "CREATE_COMPLETE", "wallet-dev-BucketArn" ]
# 3. Preview a change: what if this stack switched Env to staging? Create a named change set
aws cloudformation create-change-set --stack-name wallet-dev --change-set-name to-staging \
--use-previous-template --parameters ParameterKey=Env,ParameterValue=staging \
ParameterKey=VpcId,UsePreviousValue=true ParameterKey=LatestAmi,UsePreviousValue=true
aws cloudformation describe-change-set --stack-name wallet-dev --change-set-name to-staging \
--query 'Changes[].ResourceChange.[Action,LogicalResourceId,Replacement]' --output table
# | Modify | UploadBucket | True | <- BucketName contains Env: a NEW bucket (old one retained)
# | Modify | WebSg | True | <- GroupDescription cannot change in place
# | Modify | WebServer | False | <- instance type: stop and start, same instance
# Surprised by two replacements? That is exactly what change sets are for. Throw it away:
aws cloudformation delete-change-set --stack-name wallet-dev --change-set-name to-staging
# 4. Make drift on purpose: open port 22 in the console or with the CLI, then detect it
aws ec2 authorize-security-group-ingress --group-id sg-0123456789abcdef0 \
--protocol tcp --port 22 --cidr 0.0.0.0/0
ID=$(aws cloudformation detect-stack-drift --stack-name wallet-dev --query StackDriftDetectionId --output text)
aws cloudformation describe-stack-drift-detection-status --stack-drift-detection-id $ID \
--query '[DetectionStatus,StackDriftStatus]'
# [ "DETECTION_COMPLETE", "DRIFTED" ]
aws cloudformation describe-stack-resource-drifts --stack-name wallet-dev \
--stack-resource-drift-status-filters MODIFIED \
--query 'StackResourceDrifts[].[LogicalResourceId,PropertyDifferences[0].DifferenceType]'
# [ [ "WebSg", "ADD" ] ]
# 5. Protect the stack from deletion
aws cloudformation update-termination-protection --stack-name wallet-dev \
--enable-termination-protection
# 6. A stack created with a service role (needs iam:PassRole on it)
aws cloudformation create-stack --stack-name app-payments-api \
--template-url https://templates-example.s3.amazonaws.com/payments-api.yaml \
--role-arn arn:aws:iam::111122223333:role/cfn-app-deployer
# An error occurred (AccessDenied) ... is not authorized to perform: iam:PassRole ... <- without PassRole
# 7. A StackSet for the whole organization, with automatic deployment to new accounts
aws cloudformation create-stack-set --stack-set-name org-guardrails \
--template-body file://guardrails.yaml --capabilities CAPABILITY_NAMED_IAM \
--permission-model SERVICE_MANAGED \
--auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false
aws cloudformation create-stack-instances --stack-set-name org-guardrails \
--deployment-targets OrganizationalUnitIds=ou-ab12-cdef3456 \
--regions ap-southeast-1 ap-south-1 \
--operation-preferences MaxConcurrentPercentage=25,FailureToleranceCount=1,RegionConcurrencyType=PARALLEL
# 8. Clean up (turn termination protection off first)
aws cloudformation update-termination-protection --stack-name wallet-dev --no-enable-termination-protection
aws cloudformation delete-stack --stack-name wallet-dev
aws cloudformation wait stack-delete-complete --stack-name wallet-dev
Also try in the console: open the stack, open the visual view of the template to see the diagram (labelled View in Infrastructure Composer until AWS swaps in the console's own visual editor), and on the Drifts tab compare the expected and actual JSON of WebSg.
#Summary
Key takeaways
- CloudFormation = AWS infrastructure as code: a template (JSON or YAML) describes resources, CloudFormation creates them as a stack in dependency order, in parallel where possible, and deletes them together. Free for AWS resource types.
- Template sections:
Parameters(types,AllowedValues, SSM parameter types),Mappings,Conditions,Resources(the only required one),OutputswithExport. Functions:Ref,GetAtt,Sub,Join,FindInMap,If,ImportValue; pseudo parametersAWS::Region,AWS::AccountId. - Order: implicit through
Ref/GetAtt, explicit withDependsOn. A failed create rolls back toROLLBACK_COMPLETE, which can only be deleted; a failed update returns toUPDATE_ROLLBACK_COMPLETE.--disable-rollbackkeeps resources for debugging. CreationPolicy+cfn-init+cfn-signalmake CloudFormation wait for the software, not just the instance.- Updates are no interruption, some interruption or replacement; change sets preview them.
- Protection:
DeletionPolicy(Deletedefault,Retain,Snapshotfor EBS, RDS, ElastiCache, Redshift, Neptune, DocumentDB;RetainExceptOnCreate),UpdateReplacePolicy, stack policies (updates), termination protection (stack deletion). - Drift detection compares live resources with the template:
MODIFIED,DELETED; it does not see resources outside the stack or properties not set in the template. - Service role: CloudFormation assumes it and holds the resource permissions; the user needs CloudFormation permissions plus
iam:PassRole; without a role, the caller's own permissions are used. - Exports +
ImportValueshare values between independent stacks; nested stacks reuse components inside one lifecycle; StackSets deploy one template to many accounts and Regions, with service-managed permissions and automatic deployment through Organizations. - Around it: custom resources (Lambda), Infrastructure Composer, CDK, SAM, Terraform (not AWS), Guard and hooks, IaC generator, Git sync.
Answer on your own first, then read the explanation, even when you got it right.
Self-checkQuestion
Which section must every CloudFormation template contain?
Self-checkQuestion
A company wants every environment (dev, staging, prod) built identically, every infrastructure change reviewed in Git, and the ability to rebuild everything in another Region after a disaster. Which AWS service is designed for this?
Self-checkQuestion
In a template, an EC2 instance's security group property uses !GetAtt on a security group declared in the same template. In which order does CloudFormation create the two resources?
Self-checkQuestion
A template creates a VPC, attaches an internet gateway and allocates an Elastic IP for a NAT gateway. Creation sometimes fails because the Elastic IP is created before the gateway is attached. What fixes it?
Self-checkQuestion
A new stack fails while creating its third resource and ends in ROLLBACK_COMPLETE. What can the team do with this stack?
Self-checkQuestion
A bootstrap script fails on an EC2 instance during stack creation, and rollback deletes the instance before anyone can read its logs. What should the engineer do for the next attempt?
Self-checkQuestion
An update to a running stack fails halfway. What state does the stack normally end in?
Self-checkQuestion
An Auto Scaling group's instances install the application with cfn-init. The stack must not reach CREATE_COMPLETE until three instances report that the installation succeeded. What should be used?
Self-checkQuestion
A stack with a CreationPolicy on an instance in a private subnet always times out, although the bootstrap log shows cfn-init succeeded. What is the most likely cause?
Self-checkQuestion
Before applying a template change to production, the team wants to see exactly which resources will be modified and which will be replaced. What should they use?
Self-checkQuestion
A change set shows Replacement: True for an RDS database. What will happen if it is executed?
Self-checkQuestion
A stack holds an RDS database with customer data. If the stack is ever deleted, the team wants a final snapshot of the database. Which setting does this?
Self-checkQuestion
Which resource type does NOT support DeletionPolicy: Snapshot?
Self-checkQuestion
A team lead wants developers to be able to update a stack freely, except that no update may replace or delete the production database. Which feature fits?
Self-checkQuestion
Someone opened port 22 to 0.0.0.0/0 on a security group that belongs to a CloudFormation stack, using the EC2 console. How can the team find this through CloudFormation?
Self-checkQuestion
Drift detection reports a stack IN_SYNC, but the auditors found an EC2 instance in the same VPC that nobody declared in any template. Why did drift detection not report it?
Self-checkQuestion
A template does not set the image scanning property of an ECR repository. Someone changes that setting in the console. What does drift detection report for this property?
Self-checkQuestion
Security policy forbids developers from launching EC2 instances directly, but they must deploy approved templates that create instances. What is the least-privilege design?
Self-checkQuestion
A developer runs create-stack with --role-arn for the deployment role and immediately gets AccessDenied. She is allowed to create stacks. What is she missing?
Self-checkQuestion
A stack was created without a service role by an engineer who has CloudFormation permissions but no EC2 permissions. The template creates an instance. What happens?
Self-checkQuestion
A stack uses a powerful service role. A user without iam:PassRole but with permission to update that stack submits a template that creates an IAM user with admin rights. What happens, and why is it a risk?
Self-checkQuestion
A network team owns a VPC stack. Ten application teams deploy their own stacks into that VPC and need the VPC ID and subnet IDs. Each team updates its stack on its own schedule. What should the network stack provide?
Self-checkQuestion
The network team tries to delete its VPC stack and gets an error that an export is in use. What must happen first?
Self-checkQuestion
An organization with 60 accounts in several OUs must have the same IAM audit role and Config rules in every account and in three Regions, including accounts created in the future. What is the best solution?
Self-checkQuestion
With self-managed StackSets permissions, which IAM roles must exist?
Self-checkQuestion
A StackSet update runs in 20 accounts per Region with failure tolerance set to 1. In the first Region, two stack instances fail. What happens?
Self-checkQuestion
A template must empty an S3 bucket full of logs before CloudFormation deletes it, which no built-in resource type does. What is the CloudFormation way?
Self-checkQuestion
A team writes its infrastructure in TypeScript classes and wants AWS to manage the deployment as stacks. What do they use, and what actually deploys it?
Self-checkQuestion
A company has years of resources created by hand in the console and now wants to manage them as code without recreating them. Which feature generates a template from existing resources?
Self-checkQuestion
Which two statements about a CloudFormation service role are correct?
Choose 2
Self-checkQuestion
Which two situations call for nested stacks rather than cross-stack exports?
Choose 2
Self-checkQuestion
Which two things does drift detection NOT detect?
Choose 2
Self-checkQuestion
Which two settings protect data when a stack is deleted or a resource is replaced?
Choose 2
Self-checkQuestion
Which two statements about StackSets with service-managed permissions are correct?
Choose 2