CloudFormation stack operations — what each call changes
What CreateStack, UpdateStack, CreateChangeSet/ExecuteChangeSet, DeleteStack, ContinueUpdateRollback and RollbackStack change and need — capabilities, the stack service role (RoleARN) and iam:PassRole, OnFailure and DisableRollback, timeouts, express mode (DeploymentConfig), DeletionMode and RetainResources, the stack statuses each leaves behind, IAM condition keys, and which quotas are adjustable — with a TypeScript change-set deploy.
markdownreferencev47Q2F-SG1Rstage: buildtrust: officialservice: cloudformationinterface: cliinterface: sdkCreateStackUpdateStackCreateChangeSetExecuteChangeSetDeleteStackContinueUpdateRollbackRollbackStackCapabilitiesCAPABILITY_IAMCAPABILITY_NAMED_IAMCAPABILITY_AUTO_EXPANDRoleARN service roleiam:PassRole CloudFormationOnFailureDisableRollbackTimeoutInMinutesRetainExceptOnCreateDeploymentConfigexpress modeDeletionModeRetainResourcestermination protectionstack status codescloudformation:RoleArnCloudFormation quotaswaitUntilChangeSetCreateComplete
CloudFormation stack operations
Prefer change sets (create, review, execute) over a direct UpdateStack for anything shared. You see each resource's Add, Modify or Remove, and whether it will be replaced, before anything changes. Pass a service role (RoleARN) so that deployments don't depend on whoever runs them.
The calls
| Call | What it changes | Notes |
|---|---|---|
CreateStack | Creates the stack and every resource | OnFailure = ROLLBACK (default), DELETE or DO_NOTHING; or DisableRollback: true (not both). TimeoutInMinutes (minimum 1): after it, CREATE_FAILED, then rollback if enabled. RetainExceptOnCreate: true deletes resources created by this operation on rollback, even those with Retain |
UpdateStack | Applies the new template or parameters directly | Without changes you get No updates are to be performed. (reference/cloudformation/errors.md). --disable-rollback keeps successful changes on failure (UPDATE_FAILED) |
CreateChangeSet | Nothing yet. It computes the diff (ChangeSetType CREATE for a new stack) | Pre-deployment checks run here, as warnings only. A change set with nothing to do ends FAILED with The submitted information didn't contain changes. |
ExecuteChangeSet | Applies that change set | Deletes every other change set for the stack. --disable-rollback is allowed |
DeleteStack | Deletes resources by their DeletionPolicy (Retain and Snapshot leave billable things behind) | RetainResources (for a stack in DELETE_FAILED), DeletionMode: FORCE_DELETE_STACK (for stacks stuck in DELETE_FAILED). Termination protection refuses it |
ContinueUpdateRollback | Resumes a rollback from UPDATE_ROLLBACK_FAILED | ResourcesToSkip, --force-rollback (guides/cloudformation/fix-failed-stack.md) |
RollbackStack | Returns a *_FAILED stack (rollback disabled) to its last stable state | Deletes the stack if it never had one |
What it costs
CloudFormation calls don't carry a price of their own in the fetched sources (UNVERIFIED; check the pricing page for hooks and third-party resource types). The cost is the resources the template creates or replaces. Several things leave cost behind:
- a replacement runs the old and new resource side by side until cleanup;
RetainandSnapshotleave billable resources after a delete;- a failed create with rollback disabled leaves its successful resources running.
Review Replacement and Add rows in a change set before executing.
Capabilities
You acknowledge capabilities explicitly, or the call fails with InsufficientCapabilitiesException:
CAPABILITY_IAM: the template has IAM resources (Role, Policy, ManagedPolicy, User, Group, AccessKey, InstanceProfile, UserToGroupAddition).CAPABILITY_NAMED_IAM: those resources have custom names. It also covers unnamed ones.CAPABILITY_AUTO_EXPAND: macros (AWS::Serverless,AWS::Include) used while creating a stack directly rather than by change set. Stacks that combine macros and nested stacks must be created directly with it.
The service role (RoleARN)
- CloudFormation assumes this role for the operation and always uses it for every later operation on the stack. That holds even when the person updating can't pass the role, as long as they're allowed to operate on the stack. Give it least privilege.
- Without one, CloudFormation reuses the role already associated with the stack, or else a session from the caller's credentials. Then every deployer needs permissions for every service in the template.
- The role trusts
cloudformation.amazonaws.com, and whoever first attaches it needsiam:PassRoleon it (guides/iam/create-service-role.md). - A service role also lets slow resources (RDS, Auto Scaling, nested stacks) run past the default wait.
- To restrict which roles can be used, condition on
cloudformation:RoleArnin IAM policies. Other condition keys arecloudformation:ResourceTypes,ImportResourceTypes,TemplateUrl,StackPolicyUrl,ChangeSetNameandTargetRegion.
The CDK sets this up for you: its cfn-exec-role is the service role (reference/cdk/bootstrap-roles-and-trust.md).
Express mode (since 2026-06-30)
--deployment-config '{"mode": "EXPRESS"}' on create-stack, update-stack, delete-stack and create-change-set. In the SDK it is DeploymentConfig: { Mode: "EXPRESS" }. The CDK uses cdk deploy --express.
- The operation reports complete when each resource's create, update or delete call succeeds, not when the resource is ready. EC2 user data may still be running and CloudFront may still be deploying. Dependency order is unchanged.
- Rollback is off by default (user guide and announcement). The SDK field
DisableRollbackdocuments a default offalse, so set it explicitly. - Outputs that reference a resource attribute still wait for that resource. Custom resources still wait for their response (cap the wait with
ServiceTimeout). Nested stacks inherit the mode. StackSets aren't supported. - After an express delete, names may still be in use for a while, so a quick re-create can collide.
- Don't use it where the deployment must prove the resources work, such as production gates.
Statuses an operation leaves
| Status | Meaning | Allowed next |
|---|---|---|
CREATE_COMPLETE, UPDATE_COMPLETE, UPDATE_ROLLBACK_COMPLETE | Stable | Anything |
ROLLBACK_COMPLETE | A create failed and was cleaned up | Delete only |
CREATE_FAILED / UPDATE_FAILED with rollback disabled | Successful resources kept | Retry, update (keep --disable-rollback), RollbackStack, change sets |
UPDATE_ROLLBACK_FAILED | The rollback itself failed | ContinueUpdateRollback or delete; no change sets |
DELETE_FAILED | Some resources weren't deleted | Delete again (retain or force); no updates |
REVIEW_IN_PROGRESS | A stack ID exists but no template or resources yet (the state a not-yet-executed CREATE change set leaves, by the hub's reading) | Counts against the stack quota |
IMPORT_* | An import is running or rolling back | See reference/cloudformation/change-sets-drift-and-import.md |
DetailedStatus: CONFIGURATION_COMPLETE on an event means the resource is configured and CloudFormation is waiting for it to stabilize.
Deploy by change set (TypeScript)
import { readFile } from "node:fs/promises";
import {
CloudFormationClient, CreateChangeSetCommand, DescribeChangeSetCommand,
ExecuteChangeSetCommand, DeleteChangeSetCommand, waitUntilChangeSetCreateComplete,
waitUntilStackUpdateComplete, waitUntilStackCreateComplete,
} from "@aws-sdk/client-cloudformation";
const cfn = new CloudFormationClient({});
export async function deploy(stackName: string, templatePath: string, roleArn: string, isNew: boolean) {
const ChangeSetName = `deploy-${Date.now()}`;
await cfn.send(new CreateChangeSetCommand({
StackName: stackName,
ChangeSetName,
ChangeSetType: isNew ? "CREATE" : "UPDATE",
TemplateBody: await readFile(templatePath, "utf8"),
Capabilities: ["CAPABILITY_NAMED_IAM"],
RoleARN: roleArn,
}));
try {
await waitUntilChangeSetCreateComplete({ client: cfn, maxWaitTime: 600 }, { StackName: stackName, ChangeSetName });
} catch {
const cs = await cfn.send(new DescribeChangeSetCommand({ StackName: stackName, ChangeSetName }));
if (cs.StatusReason?.includes("didn't contain changes")) {
await cfn.send(new DeleteChangeSetCommand({ StackName: stackName, ChangeSetName }));
return "no changes";
}
throw new Error(`Change set failed: ${cs.StatusReason}`);
}
const cs = await cfn.send(new DescribeChangeSetCommand({ StackName: stackName, ChangeSetName }));
for (const c of cs.Changes ?? []) {
const r = c.ResourceChange;
console.log(r?.Action, r?.LogicalResourceId, r?.ResourceType, "replacement:", r?.Replacement);
}
await cfn.send(new ExecuteChangeSetCommand({ StackName: stackName, ChangeSetName }));
const wait = isNew ? waitUntilStackCreateComplete : waitUntilStackUpdateComplete;
await wait({ client: cfn, maxWaitTime: 3600 }, { StackName: stackName });
return "deployed";
}
When a wait fails, find the root event with firstFailure() in guides/cloudformation/fix-failed-stack.md.
Quotas
The values aren't typed here; look them up in Service Quotas.
- Adjustable: stacks per account, StackSets.
- Not adjustable (the page gives workarounds only):
- resources per stack: split into nested stacks;
- template body size: upload to S3, nest, or minify;
- parameters: use mappings or lists;
- mappings and outputs;
- resources per nested-stack operation.
The CDK warns at 80% of the resource limit (reference/cdk/errors.md).