Course outline

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, Outputs with 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 with CreationPolicy and cfn-signal, updates with change sets, and deletion with DeletionPolicy.
  • 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:

BenefitWhat it means in practice
No manual resourcesEverything in the stack came from the template, so the template is the documentation.
Code reviewInfrastructure changes go through Git and pull requests like application code (“version control for infrastructure”).
RepeatableThe same template builds dev, staging and prod, in any Region or account, with different parameters.
Delete in one goDeleting the stack deletes everything it created, in the right order.
Cost per stackCloudFormation 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 devA schedule can delete the dev stack at 5 PM and recreate it at 8 AM, safely, because the template rebuilds it identically.
Disaster recoveryThe recovery Region needs only the templates (and data copies) to rebuild the environment.
No reinventingAWS 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
SectionRequired?What it holds
AWSTemplateFormatVersionNoThe format version; 2010-09-09 is the only value.
DescriptionNoFree text, up to 1,024 bytes.
MetadataNoExtra data, for example the interface layout of the console, or cfn-init configuration.
ParametersNoInputs given at create or update time (up to 200).
RulesNoChecks across parameters before anything is created (“prod needs a large instance type”).
MappingsNoFixed lookup tables (per Region, per environment), read with Fn::FindInMap.
ConditionsNoBooleans that decide whether a resource, property or output exists.
TransformNoMacros that rewrite the template first, such as AWS::Serverless-2016-10-31 for SAM.
ResourcesYesThe resources to create (up to 500 per template), each with a logical ID, a Type and Properties.
OutputsNoValues 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 as AWS::EC2::VPC::Id or AWS::EC2::KeyPair::KeyName, which the console shows as drop-down lists and CloudFormation validates. AllowedValues, AllowedPattern, MinValue/MaxValue reject bad input before anything is built. NoEcho: true hides 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::Value of the type AWS::EC2::Image::Id with 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 Env is prod. A resource with Condition: IsProd simply does not exist in dev.

#Intrinsic functions and pseudo parameters

Function (short YAML form)ReturnsExample
!RefA parameter's value, or a resource's main identifier (instance ID, bucket name, security group ID…)!Ref VpcId
!GetAttAnother attribute of a resource!GetAtt UploadBucket.Arn, !GetAtt Db.Endpoint.Address
!SubA string with ${...} placeholders filled in!Sub 'arn:aws:s3:::${UploadBucket}/*'
!JoinValues joined with a delimiter!Join [',', [a, b, c]]
!FindInMapA value from Mappings!FindInMap [EnvSize, !Ref Env, InstanceType]
!IfOne of two values, by condition!If [IsProd, Enabled, Suspended]
!ImportValueAn export of another stack!ImportValue wallet-network-VpcId
!Equals, !And, !Or, !NotBooleans for Conditions!Equals [!Ref Env, prod]
!Select, !Split, !GetAZs, !Cidr, !Base64Lists, 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 !Ref or !GetAtt on another resource depends on it. WebServer uses WebSg.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.

Templatewallet.yaml
CloudFormationwallet-devCREATE_IN_PROGRESS
Security group
EC2 instance
S3 bucket
create-stack
Step 1 of 4
1

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

StatusMeaning
CREATE_IN_PROGRESS → CREATE_COMPLETECreation running, then finished successfully.
CREATE_FAILEDA resource failed; with rollback disabled the stack stays here for debugging.
ROLLBACK_IN_PROGRESS → ROLLBACK_COMPLETECreation failed and everything created so far was deleted. The stack can only be deleted.
UPDATE_IN_PROGRESS → UPDATE_COMPLETE_CLEANUP_IN_PROGRESS → UPDATE_COMPLETEAn update, then removal of replaced old resources.
UPDATE_ROLLBACK_IN_PROGRESS → UPDATE_ROLLBACK_COMPLETEAn update failed and the stack returned to its last working state. It is usable and can be updated again.
UPDATE_ROLLBACK_FAILEDEven the rollback failed (often because someone deleted a resource by hand). Fix the cause, then continue update rollback.
DELETE_IN_PROGRESS → DELETE_COMPLETE / DELETE_FAILEDDeletion; 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 in CREATE_FAILED. Inspect the instance and its logs, then fix and retry with an update, or roll back explicitly with rollback-stack.
  • --retain-except-on-create keeps resources whose DeletionPolicy says Retain, 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 behaviourWhat happensExample
No interruptionChanged in place; the resource keeps working and keeps its IDA security group rule, tags, an S3 lifecycle rule
Some interruptionChanged in place, with a short outageAn EC2 instance type (the instance is stopped and started)
ReplacementA new resource is created, references are switched to it, then the old one is deletedAn 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

ControlProtects againstHow
DeletionPolicyLosing a resource when it is removed from the template or the stack is deletedDelete (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)
UpdateReplacePolicyLosing the old resource when an update replaces itSame values: Delete, Retain, Snapshot
Stack policyUpdates to critical resources through CloudFormationA 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 protectionSomeone deleting the whole stackupdate-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.

Engineer
CloudFormationwallet-prod
Web serveri-0aaa
Wallet DB
create-change-set
Step 1 of 3
1

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 statusMeaning
IN_SYNCMatches the template
MODIFIEDA property differs; the details show expected vs actual (NOT_EQUAL, ADD, REMOVE)
DELETEDThe resource was deleted outside CloudFormation
NOT_CHECKEDThis 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:

  1. Put the resource back by hand to match the template, or
  2. Change the template to match reality if the manual change was right, and update the stack, or
  3. 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.
  4. 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 type IMPORT.

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.

Junior adminconsole
Web SG
CPU alarm
CloudFormationorders-prodUPDATE_COMPLETE
Template
add port 22
Step 1 of 5
1

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:

WhoNeedsExample
The usercloudformation:CreateStack, UpdateStack, DeleteStack, CreateChangeSet… (often scoped to stack names), and iam:PassRole on the rolecloudformation:* on stack/app-*, iam:PassRole on role/cfn-app-deployer
The service roleThe permissions for the resources in the templateec2:* 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 restrict cloudformation:UpdateStack on sensitive stacks.
  • Without a service role, a user without ec2:RunInstances can still call CreateStack; the stack starts, the instance fails with AccessDenied, 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.

Developerno EC2 rights
IAM
CloudFormation
EC2
create-stack --role-arn
Step 1 of 6
1

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::Stack resources 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.

Network stackexports VPC ID
Orders stack
Payments stack

Exports

wallet-network-VpcId

vpc-0abc1234

wallet-network-PrivateSubnets

subnet-0a…, subnet-0b…

Step 1 of 2
1

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, AWSCloudFormationStackSetAdministrationRole in the administrator account and AWSCloudFormationStackSetExecutionRole in 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.

Workloads OU
Admin account
StackSetsorg-guardrails
Billing
Portal
Monitoring
create-stack-set
Step 1 of 4
1

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

ToolWhat it isRelation to CloudFormation
AWS Infrastructure ComposerA 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 CDKInfrastructure in TypeScript, Python, Java, C# or Go (lesson 1.3)cdk synth generates a CloudFormation template; cdk deploy deploys it as a stack
AWS SAMShort 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 BeanstalkPlatform for web apps (lesson 9.4)Each environment is a CloudFormation stack Beanstalk writes for you
TerraformHashiCorp's multi-cloud IaC tool, written in HCL, with its own state fileNot CloudFormation: calls the AWS APIs directly. The exam's AWS-native answer is CloudFormation
CloudFormation GuardPolicy as code: rules such as “every bucket must have encryption”, checked with cfn-guard validate in a pipelineAlso 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 themImport 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 repositoryA push to the branch updates the stack

#Which answer?

The question saysAnswerNear miss and why it loses
Infrastructure as code, repeatable environmentsCloudFormationElastic Beanstalk deploys an app platform, not arbitrary infrastructure; OpsWorks is retired
IaC in TypeScript or PythonAWS CDK (synthesizes CloudFormation)SAM is YAML for serverless
Preview changes, which resources will be replacedChange setDrift detection compares live vs template
Someone changed resources manuallyDrift detectionChange sets compare two templates; CloudTrail tells who, not how far from the template
Keep the database when the stack is deletedDeletionPolicy: Retain or SnapshotTermination protection stops the whole delete; a stack policy covers updates only
Prevent updates to the production database through the stackStack policyIAM on the user does not distinguish logical resources in the stack
Prevent accidental stack deletionTermination protectionDeletionPolicy keeps resources but the stack is still deleted
Developers deploy but cannot create EC2 directlyService role + iam:PassRoleGiving developers ec2:* breaks least privilege
Same stack in many accounts and Regions, new accounts automaticallyStackSets, service-managed, automatic deploymentNested stacks stay in one account and Region
Share a VPC ID with many independent stacksOutputs with Export + Fn::ImportValueNested stacks bind the lifecycles together
Reuse a standard component in many templatesNested stacks (or modules)Copy-paste drifts apart
Wait until the instance's software is installedCreationPolicy + cfn-signal (with cfn-init)DependsOn only waits for the resource to be created
Do something CloudFormation has no resource forCustom resource (Lambda-backed)A script outside the stack is not part of its lifecycle
Template from existing resourcesIaC generatorDrift 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 first CREATE_FAILED event, delete the stack, fix, create again. Use --disable-rollback to 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_FAILED on 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 Replacement column of the change set; protect the database with a stack policy, DeletionPolicy: Snapshot and UpdateReplacePolicy: 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 CreationPolicy timeout: cfn-signal never ran (script error, helper scripts missing) or could not reach CloudFormation from a private subnet without NAT or an endpoint.
  • AccessDenied on iam:PassRole: the user may create stacks but not pass that role. Allow iam:PassRole on the specific role ARN.
  • AccessDenied from 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_SYNC but 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 OUTDATED until 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 finally block, and set a shorter ServiceTimeout.

#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-lint and 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), Outputs with Export. Functions: Ref, GetAtt, Sub, Join, FindInMap, If, ImportValue; pseudo parameters AWS::Region, AWS::AccountId.
  • Order: implicit through Ref/GetAtt, explicit with DependsOn. A failed create rolls back to ROLLBACK_COMPLETE, which can only be deleted; a failed update returns to UPDATE_ROLLBACK_COMPLETE. --disable-rollback keeps resources for debugging.
  • CreationPolicy + cfn-init + cfn-signal make CloudFormation wait for the software, not just the instance.
  • Updates are no interruption, some interruption or replacement; change sets preview them.
  • Protection: DeletionPolicy (Delete default, Retain, Snapshot for 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 + ImportValue share 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.