Based on the DVA-C02 exam guideReport an error
Clicking to create one server in the console is easy. But creating tens of resources — VPC, subnets, security groups, EC2, RDS, ALB, Lambda — clicking through all of them, replicating them identically to staging and production, then six months later trying to trace "why is this security group wide open?" — that's where hell begins. Hand-crafted infrastructure can't be reproduced, has no change history, and depends only on human memory. Infrastructure as Code (IaC) attacks this problem head-on — declaring infrastructure in text files enables version control (git), code review, and recreating identical environments with a single command. AWS CloudFormation is AWS's implementation of IaC.
In DVA-C02, CloudFormation is central to the Deployment domain and the foundation for SAM and CDK, so you must understand it. Simple memorization (!Ref vs !GetAtt, DeletionPolicy) appears, but scenario questions asking "why is stack update dangerous," "how does rollback work," and "cross-stack reference traps" are more common. This article digs deep into: what's different between declarative IaC and imperative scripts, how CloudFormation resolves dependencies to determine creation order, how stack update and rollback work internally, and why various protection mechanisms diverge.
When you first see a CloudFormation template, it seems like "just a config file," but underneath is the important philosophy of the declarative model. Declarative IaC specifies only "what you want (the desired final state)" and leaves "how to achieve it (creation order, API calls, error handling, rollback)" to the CloudFormation engine. In contrast, bash scripts doing aws ec2 create-vpc → aws ec2 create-subnet → ... in order are imperative.
This difference is decisive in practice. Imperative scripts that "halfway fail" leave you in a messy state — VPC created but subnet failed, and you own the cleanup. Declarative models let the engine compute the difference between "desired state" and "current state," so running twice yields the same result — idempotency — and on failure, the engine automatically rolls back. Not "do what I tell you" but "make this state happen."
AWSTemplateFormatVersion: '2010-09-09'
Description: My Application Stack
Parameters:
EnvironmentName:
Type: String
Default: production
AllowedValues: [production, staging, development]
Conditions:
IsProduction: !Equals [!Ref EnvironmentName, production]
Mappings:
RegionAMI:
ap-northeast-2: { AMI: ami-0c9c942bd7bf113a2 }
us-east-1: { AMI: ami-0abcdef1234567890 }
Resources:
MyVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
Tags: [{ Key: Name, Value: !Sub "${EnvironmentName}-vpc" }]
MyEC2:
Type: AWS::EC2::Instance
Properties:
InstanceType: !If [IsProduction, m5.large, t3.micro]
ImageId: !FindInMap [RegionAMI, !Ref AWS::Region, AMI]
Outputs:
VPCId:
Value: !Ref MyVPC
Export:
Name: !Sub "${EnvironmentName}-vpc-id"💡 Related theory: The core value of the declarative model is convergence — the system reaches the declared goal state regardless of starting point. This is the shared philosophy of configuration management tool lineage (Puppet 2005, Chef 2009, Ansible 2012) and, more fundamentally, resembles functional programming's "describe what to compute, let the runtime handle how." CloudFormation, Terraform, and Kubernetes manifests are all declarative because in distributed infrastructure, "humans handling every order and error by hand" is practically impossible. Handing that complexity to the engine is the essence of declarative.
When you specify an EC2, the VPC it belongs to, and the security group in a template, how does CloudFormation know what to create first? Answer: dependency graph. CloudFormation parses the template and extracts resource references — if EC2 references VPC via !Ref MyVPC, an edge "EC2 depends on VPC" is created. The resulting directed graph is topologically sorted, so dependencies are built first (VPC → subnet → EC2), and independent resources are created in parallel.
So in most cases, you don't need to explicitly declare DependsOn — referencing another resource via !Ref or !GetAtt automatically infers dependency. DependsOn is only for "implicit dependencies" that auto-inference misses (e.g., an IAM role must propagate before a resource using it is created).
🔍 Going deeper: The difference between
!Refand!GetAttextends beyond simple memorization to dependency graph operation.!Ref MyVPCusually returns the resource's "default identifier" (for VPC, the VPC ID), while!GetAtt MyRDS.Endpoint.Addressgrabs a specific attribute. Both create "this value requires that resource to exist first" dependency. But!GetAttattributes sometimes only exist after resource is fully provisioned (RDS endpoint address only known when DB is up). So a resource referencing RDS endpoint via!GetAttmust wait for RDS creation to complete, causing longer stack creation — a common reason for slow stack creates.
⚠️ Trap: If two resources reference each other, circular dependency occurs and stack creation fails. For example, security group A references B for inbound and B references A — topological sort is impossible. Solution: instead of inline rules, separate
SecurityGroupIngressresources to break the cycle. On exams, "circular dependency" error almost always points to this pattern.
The most dangerous moment in CloudFormation is not creation but update. When changing an existing production stack's template, CloudFormation handles each changed resource one of three ways:
Replacement is scary for good reason: if an RDS database is replaced, a new empty DB is created and existing data vanishes. So not knowing "which properties trigger replacement" and modifying the template can cause data loss. That's why Change Set exists.
aws cloudformation create-change-set \
--stack-name my-stack --template-body file://template.yaml \
--change-set-name my-changes
aws cloudformation describe-change-set \
--stack-name my-stack --change-set-name my-changes # Check Replacement: True
aws cloudformation execute-change-set \
--stack-name my-stack --change-set-name my-changesChange Set shows "what will be added/modified/deleted/replaced" before execution. As a safety gate for production deploy, you can spot "DB will be replaced" and stop it.
📚 Case study: Accidentally triggering replacement of RDS by changing
DBInstanceIdentifieror certain engine properties and losing data is a classic IaC operations trap. AWS recommends two-layer defense. First,DeletionPolicy: SnapshotorRetainon RDS/EBS to preserve data on delete/replacement. Second, always review Change Set for Replacement before updating. That's why "never update directly, always go through Change Set" became doctrine in mature teams.
The strongest safety net of the declarative model is automatic rollback. If a resource fails during stack create or update, CloudFormation doesn't leave what it built — it rolls back to the previous stable state. On creation failure, everything made is deleted (ROLLBACK_COMPLETE); on update failure, the previous template state is restored (UPDATE_ROLLBACK_COMPLETE). This is the decisive difference from imperative scripts — scripts leave you halfway done, CloudFormation promises "all success" or "all undo," like a transaction.
💡 Related theory: This "all or nothing" is the atomicity of database transactions. Think of CloudFormation as an "infrastructure transaction manager" — bundling multiple resource creates as one unit, rolling back (with compensating deletes) on midway failure. But it's not a perfect transaction. Some resources won't delete on rollback (e.g., non-empty S3 buckets), leaving stacks stuck in
UPDATE_ROLLBACK_FAILED. Then you manually clean andContinueUpdateRollbackto resume. On exams, "stack stuck in ROLLBACK_FAILED" has this fix.
⚠️ Trap: If a newly-created resource has
DeletionPolicy: Retainduring rollback, it stays even after rollback. Also, for CloudFormation to know if EC2 bootstrapping finished, it needsCreationPolicyand cfn-signal — without it, CloudFormation only sees "EC2 API succeeded" and doesn't know if the app inside actually launched. Broken UserData scripts succeed the stack anyway — exam trap from here.
When one stack gets too large, managing it's hard. Two ways to decompose infrastructure exist, and their purposes differ.
| Way | Mechanism | Coupling | Suited for |
|---|---|---|---|
| Cross-stack reference | Export + !ImportValue | Loose | Network stack's VPC ID shared by multiple app stacks |
| Nested stack | Parent includes child template as AWS::CloudFormation::Stack | Tight | Reusable components (standard ALB config) in multiple places |
Cross-stack reference: one stack Exports values in Outputs, another !ImportValues them. Loosely coupled but has constraints — if something Imports an Exported value, you can't change that Export or delete the exporting stack**. Dependency tightens. Also, **!ImportValue` only works within same region/account (no cross-region/account).
Nested stacks: parent stack wraps child stacks as resources. Good for standardized building blocks, and deleting parent also cleans up children.
🔍 Going deeper: The tight dependency created by
Export/ImportValueoften causes operational headaches. If network stack Exports VPC ID and five app stacks Import it, changing that Export name requires detaching all five first. So large organizations increasingly store values in SSM Parameter Store and read from it — Parameter Store doesn't create this tight coupling, letting stacks change independently. That's one reason CDK was created — to handle cross-stack reference more flexibly in code.
CloudFormation has similar-looking but different-target mechanisms, a single-exam trap. "What are we protecting from what" clarifies.
| Mechanism | What does it prevent | Scope |
|---|---|---|
| DeletionPolicy | Resource disappearing on stack/resource delete (Retain/Snapshot/Delete) | Per resource, at delete time |
| UpdateReplacePolicy | Resource disappearing when update causes replacement | Per resource, at replacement time |
| Stack Policy | Modifying/replacing resource during update | Stack update operation |
| Termination Protection | Stack itself being deleted | Stack-level |
| IAM Policy | Who can manipulate the stack | User/role permissions |
Separate the oft-confused pairs. DeletionPolicy vs Termination Protection: former is "keep just this DB when deleting stack" (per resource), latter is "prevent this stack from being deleted entirely" (stack-level). Stack Policy vs IAM: former is "don't touch ProductionDB during update" (operation protection), latter is "John can't update stacks" (people protection).
{
"Statement": [
{ "Effect": "Allow", "Action": "Update:*", "Principal": "*", "Resource": "*" },
{
"Effect": "Deny",
"Action": ["Update:Replace", "Update:Delete"],
"Principal": "*",
"Resource": "LogicalResourceId/ProductionDB"
}
]
}⚠️ Trap: "Prevent accidental DB deletion?" has different answers by scenario. "Keep DB when stack deletes" →
DeletionPolicy: Retain. "Prevent DB replacement during update" → Stack Policy's DenyUpdate:Replace. "Prevent stack itself from deletion" → Termination Protection. Know only one and you fall into the trap — protection time (delete/update) and scope (resource/stack) matter together.
IaC's premise is "template is the truth of infrastructure." But if someone clicks a security group open in the console, template (code) and actual infrastructure (reality) diverge. That's drift. CloudFormation's Drift Detection compares expected state from template to actual resource state and shows differences.
Important: drift detection is not automatic — you must trigger it explicitly. CloudFormation doesn't prevent console changes in real-time (that's IAM/SCP's job). Drift detection is a post-hoc "find the divergence that's already happened" tool. For automatic/continuous detection, combine with AWS Config rules.
📚 Case study: Many teams repeat "it's urgent, fix in console now, update code later," then next stack update throws console changes away, losing emergency patches. CloudFormation update assumes "template is truth" and applies based on that, so ignoring drift and updating overwrites console changes. Mature teams prevent console modification via IAM (read-only) and make all changes go through template → pipeline, enforcing immutable infrastructure discipline.
CloudFormation's core thesis is "declare desired infrastructure state, the engine resolves order via dependency graph, and deploys or entirely rolls back as a transaction." Declarative model yields idempotency and automatic rollback, dependency graphs and topological sort handle order/parallelization automatically. Update risk lies in replacement (dangerous for stateful resources), and Change Set previews it. DeletionPolicy, Stack Policy, and Termination Protection each protect different things at different times — resource vs stack, delete vs update. Cross-stack Export coupling is loosened with SSM Parameter Store, drift signals "code as truth" broke. Most exam traps ask "delete or replace, resource or stack, automatic or manual" on this pipeline.
Next we move to SAM, CloudFormation compressed for serverless.
Click a choice to reveal the answer and explanation.
Question 1
You want to modify and apply the template of a production stack. How can you discover in advance that RDS would be unintentionally replaced, causing data loss?
Question 2
When creating a CloudFormation stack, two security groups reference each other and a "circular dependency" error occurred. What is the correct fix?
Question 3
You want to preserve the production RDS database inside a stack even if the stack is deleted. What is the appropriate setting?
Question 4
You want to reference, in an application stack, the VPC ID exported by a network stack. What are the correct function and its constraint?
Question 5
You create an EC2 instance with CloudFormation and bootstrap the application with UserData, but the stack finishes as "successful" even if the script fails. How can you make CloudFormation wait for bootstrap completion?
Question 6
An operator modified a security group by hand in the console. How can you check the difference between the CloudFormation template and the actual resource?
Question 7
Which statement about CloudFormation's automatic rollback behavior is the most accurate?