---
cuppy_hub: aws
cuppy_path: reference/cloudformation/stack-operations.md
cuppy_section: docs
cuppy_doc: 7Q2F-SG1R
cuppy_doc_uuid: 25a09fc5-40dc-4803-9127-e07032d43b5f
cuppy_permalink: https://cuppy.io/aws/d/7Q2F-SG1R
cuppy_version: 4
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/aws/docs/reference/cloudformation/stack-operations
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: b23c7948fad4c7a55285767eb5f8a5fc0651d01fab37322ff04d7f2f33321684
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# 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;
- `Retain` and `Snapshot` leave 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 needs `iam:PassRole` on 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:RoleArn` in IAM policies. Other condition keys are `cloudformation:ResourceTypes`, `ImportResourceTypes`, `TemplateUrl`, `StackPolicyUrl`, `ChangeSetName` and `TargetRegion`.

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 `DisableRollback` documents a default of `false`, 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)

```ts
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`).


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 131 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/aws/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
