Stack Creation, Updates & Change Sets
Calling UpdateStack directly applies a change immediately, with no chance to see what it will do first. For anything beyond a toy stack, that is a risk you do not need to take.
Search across all documentation pages
Calling UpdateStack directly applies a change immediately, with no chance to see what it will do first. For anything beyond a toy stack, that is a risk you do not need to take.
Change sets solve this. A change set is a preview: CloudFormation computes what would happen to every resource in the stack, without touching anything, and lets you inspect that plan before you decide to run it.
The pattern is three calls: CreateChangeSet to compute the plan, DescribeChangeSet to read it, and ExecuteChangeSet to apply it once you are satisfied.
# --- Python (boto3) ---
import boto3
updated_template = """
Resources:
MyBucket:
Type: AWS::S3::Bucket
Properties:
VersioningConfiguration:
Status: Enabled
"""
cfn = boto3.client("cloudformation", region_name="us-east-1")
cfn.create_change_set(
StackName="my-first-stack",
ChangeSetName="enable-versioning",
TemplateBody=updated_template,
ChangeSetType="UPDATE",
)// --- TypeScript (AWS SDK v3) ---
import { CloudFormationClient, CreateChangeSetCommand } from "@aws-sdk/client-cloudformation";
const updatedTemplate = `
Resources:
MyBucket:
Type: AWS::S3::Bucket
Properties:
VersioningConfiguration:
Status: Enabled
`;
const cfn = new CloudFormationClient({ region: "us-east-1" });
await cfn.send(new CreateChangeSetCommand({
StackName: "my-first-stack",
ChangeSetName: "enable-versioning",
TemplateBody: updatedTemplate,
ChangeSetType: "UPDATE",
}));ChangeSetType: "UPDATE" targets an existing stack; "CREATE" computes the plan for a brand-new one, which is how you can preview a first deploy the same way you preview an update.
CreateChangeSet returns immediately while CloudFormation computes the plan in the background, so wait for it, then read Changes from DescribeChangeSet.
# --- Python (boto3) ---
import boto3
cfn = boto3.client("cloudformation", region_name="us-east-1")
waiter = cfn.get_waiter("change_set_create_complete")
waiter.wait(StackName="my-first-stack", ChangeSetName="enable-versioning")
resp = cfn.describe_change_set(
StackName="my-first-stack",
ChangeSetName="enable-versioning",
)
for change in resp["Changes"]:
detail = change["ResourceChange"]
print(detail["Action"], detail["LogicalResourceId"], detail.get("Replacement"))// --- TypeScript (AWS SDK v3) ---
import {
CloudFormationClient,
DescribeChangeSetCommand,
waitUntilChangeSetCreateComplete,
} from "@aws-sdk/client-cloudformation";
const cfn = new CloudFormationClient({ region: "us-east-1" });
await waitUntilChangeSetCreateComplete(
{ client: cfn, maxWaitTime: 300 },
{ StackName: "my-first-stack", ChangeSetName: "enable-versioning" },
);
const resp = await cfn.send(new DescribeChangeSetCommand({
StackName: "my-first-stack",
ChangeSetName: "enable-versioning",
}));
for (const change of resp.Changes ?? []) {
const detail = change.ResourceChange;
console.log(detail?.Action, detail?.LogicalResourceId, detail?.Replacement);
}Each entry in Changes names an Action (Add, Modify, or Remove), the resource it targets, and - critically - a Replacement flag (True, False, or Conditional) telling you whether CloudFormation will destroy and recreate the resource rather than update it in place.
Replacement: "True" is the single most important field to check. Many properties can be changed in place - like enabling versioning on a bucket - but others force a replacement, meaning the existing resource is deleted and a new one created with a new physical ID.
For a stateless resource that is often fine. For a database, a stateful queue, or anything holding data your application depends on, a replacement can mean silent data loss the moment ExecuteChangeSet runs. Reviewing Changes before executing is exactly what change sets are for - catching this before it happens rather than after.
Once you have reviewed the plan and are ready to proceed, ExecuteChangeSet applies it:
# --- Python (boto3) ---
import boto3
cfn = boto3.client("cloudformation", region_name="us-east-1")
cfn.execute_change_set(
StackName="my-first-stack",
ChangeSetName="enable-versioning",
)
cfn.get_waiter("stack_update_complete").wait(StackName="my-first-stack")// --- TypeScript (AWS SDK v3) ---
import {
CloudFormationClient,
ExecuteChangeSetCommand,
waitUntilStackUpdateComplete,
} from "@aws-sdk/client-cloudformation";
const cfn = new CloudFormationClient({ region: "us-east-1" });
await cfn.send(new ExecuteChangeSetCommand({
StackName: "my-first-stack",
ChangeSetName: "enable-versioning",
}));
await waitUntilStackUpdateComplete({ client: cfn, maxWaitTime: 600 }, { StackName: "my-first-stack" });If a change set turns out to be unwanted after review, DeleteChangeSet discards it without touching the stack. Nothing is applied until ExecuteChangeSet is called, which is what makes the whole workflow safe to compute speculatively.
This three-step pattern maps naturally onto a two-phase CI pipeline: one job creates the change set and posts the diff for human review (or an automated policy check for dangerous replacements), and a second, separate job executes it after approval. That mirrors how cdk deploy and aws cloudformation deploy behave under the hood, just with the review step made explicit and scriptable.
UpdateStack out of habit. It applies immediately with no preview - use the change-set workflow for anything beyond a disposable test stack.CreateChangeSet to finish. The change set is computed asynchronously; DescribeChangeSet called too early returns a status of CREATE_PENDING or CREATE_IN_PROGRESS with no Changes yet.Replacement field. An Action: Modify entry can still replace the resource - always check Replacement, not just Action.CreateChangeSet completes with a status indicating no changes to execute - treat this as a no-op, not an error.DescribeStacks-adjacent tooling does not surface confusing leftovers.UpdateStack directly for low-stakes, disposable stacks where a preview step adds friction without adding safety.aws cloudformation deploy (CLI) which wraps the same create-execute change-set pattern in one command, ideal for a human running a deploy from a terminal.cdk deploy for CDK apps, which performs the same change-set review and prompts interactively - see the CDK synthesis page for when to bypass it.Use explicit change sets whenever a person or an automated gate should see the plan before it runs; skip straight to UpdateStack only when the stack is truly disposable.
A computed preview of what an update (or a new stack creation) would do, without applying anything. You inspect it with DescribeChangeSet, then choose to run it with ExecuteChangeSet or discard it with DeleteChangeSet.
UpdateStack applies immediately with no preview. A change set lets you catch dangerous changes, especially resource replacements, before anything is touched.
Check the Replacement field on each entry in DescribeChangeSet's Changes list. It is "True", "False", or "Conditional".
Yes. Set ChangeSetType to "CREATE" instead of "UPDATE" to preview a first deploy the same way you preview an update.
Yes. It computes the plan asynchronously; wait with the change_set_create_complete waiter (boto3) or waitUntilChangeSetCreateComplete (SDK v3) before calling DescribeChangeSet.
CreateChangeSet completes with a status indicating there is nothing to execute. Treat it as a no-op rather than an error.
Call DeleteChangeSet. The stack is untouched either way - nothing is applied until ExecuteChangeSet runs.
Yes. Both wrap the same CreateChangeSet / ExecuteChangeSet pair, just with the review step folded into their own CLI output and prompts.
CreateStack / UpdateStack calls this page adds a safety layer around.cdk deploy.Stack versions: This page was written for boto3 1.43.x (Python 3.10+) and the AWS SDK for JavaScript v3 (Node.js 18+).
Reviewed by Chris St. John·Last updated Jul 25, 2026