skip to content

A CDK stack defines a Lambda function with `lambda.Code.fromAsset('./handler')` and a container image built from a local Dockerfile. Walk through what `cdk deploy` does with those assets before CloudFormation ever updates the stack.

level: middleimportance: should knowfreq 48%

answer

  1. content hash decides the object key
  2. staged at synth, published at deploy
  3. upload before the stack update
  4. bootstrap bucket and ECR repository
  5. identical content uploads nothing

basics

~20 s

Synthesis stages each asset into cdk.out and records a content hash in the stack's asset manifest. cdk deploy then publishes the zip to the bootstrap staging S3 bucket and the image to the bootstrap ECR repository, and only then updates the stack.

solid answer

~50 s

Deployment is two phases. First synthesis stages the assets: the handler directory is zipped into `cdk.out/asset.<hash>`, the image asset is recorded, and both are listed in `<StackName>.assets.json` with a **content hash** and a destination. The template no longer refers to `./handler` — it refers to an S3 bucket and object key, and to an ECR image URI. Then `cdk deploy` publishes: it assumes the bootstrap file-publishing role and uploads the zip to `cdk-<qualifier>-assets-<account>-<region>` if that key is not already there, builds the image with Docker and pushes it to the bootstrap ECR repository, and only afterwards assumes the deploy role and hands CloudFormation the template. Because the key is the content hash, an unchanged asset is skipped and produces no template change; changing one line of handler code changes the hash, changes the object key in the template, and is what makes CloudFormation update the function.

code

typescript · 15 lines
typescript
import { Stack, StackProps } from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';

export class ApiStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    new lambda.Function(this, 'Handler', {
      runtime: lambda.Runtime.NODEJS_20_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('./handler'),
    });
  }
}

go deeper

for a junior

Know that local code and Dockerfiles are called assets and that they are uploaded to AWS during cdk deploy — the template only ever carries a pointer to where they landed.

for a middle

Trace the two phases in order: synth stages and hashes the asset, deploy publishes it to the bootstrap bucket or ECR, and only then does the stack update run. Explain why the hash appears in the object key.

for a senior

Show the operational consequences: the runner needs Docker and the right assume-role permissions, the staging bucket accumulates objects that rollbacks may still need, and hotswap is a dev-only shortcut.

for a principal

Own the artifact-promotion story — publishing one synthesized assembly into several accounts, how asset retention interacts with rollback guarantees, and what policy governs pruning a bucket every deployment depends on.

## What counts as an asset An asset is any local file or directory that has to exist in AWS before CloudFormation can use it. The common cases are a Lambda code bundle (`lambda.Code.fromAsset`), a Docker image built from a local Dockerfile, a file uploaded by an S3 deployment construct, and a synthesized template too large to submit inline and therefore staged in S3 itself. CloudFormation cannot read your laptop, so all of these must be published first. ## Phase one: staging at synth time During synthesis the CDK computes a hash of the asset's content, copies or zips the content into the cloud assembly as `asset.<hash>` (or `asset.<hash>.zip`), and writes an entry into `<StackName>.assets.json` naming the source, the destination bucket or repository, and the object key or image tag. Simultaneously the template's `Code` property is rendered as an S3 bucket/key pair, and the image reference as an ECR URI. That is why the cloud assembly is self-contained: the templates plus the asset manifest plus the staged bytes are everything a later stage needs. A pipeline can synthesize once, keep `cdk.out` as a build artifact, and publish those exact bytes into several accounts. ## Phase two: publishing at deploy time `cdk deploy` reads the asset manifest and, for each entry, assumes the matching bootstrap role — the file-publishing role for S3, the image-publishing role for ECR — in the target account. It checks whether the object key already exists; if it does, the upload is skipped, because the key *is* the content hash and identical content is already there. Image assets require a working Docker daemon on the machine running the deploy, which is a frequent CI surprise: an environment that runs `cdk synth` happily will fail at deploy if it cannot build images. Only when publishing succeeds does the CLI assume the deploy role and submit the template to CloudFormation, which in turn uses the bootstrap execution role to make the changes. Ordering matters: if the stack update ran first, the Lambda service would be told to fetch an S3 object that had not been uploaded yet. ## Why content hashing is the whole design Content addressing gives you two properties at once. Idempotence: redeploying unchanged code uploads nothing and produces a byte-identical template, so CloudFormation reports no changes. And change detection: editing one line changes the hash, therefore the object key, therefore a property in the template, therefore a real stack update. This explains a question candidates often get wrong — why the CDK does not simply overwrite one fixed S3 object with the new zip. CloudFormation only acts when a *template property* changes; if the bucket and key stayed constant, the template would be unchanged, CloudFormation would see nothing to do, and your new code would never be picked up. Immutable, hash-named objects are what make the update visible. ``` MyStack.assets.json (abridged) files: 3f9c...a1: source: { path: "asset.3f9c...a1", packaging: "zip" } destinations: 123456789012-eu-west-1: bucketName: cdk-hnb659fds-assets-123456789012-eu-west-1 objectKey: 3f9c...a1.zip ``` ## Operational consequences **The staging bucket grows.** Every distinct build leaves an object behind, and nothing prunes them by default. Old assets cannot be deleted carelessly either — a deployed stack references its current asset, and a rollback may need a previous one. Recent CDK CLI versions ship a `cdk gc` command to garbage-collect unreferenced assets; it is still experimental, so many teams instead accept the storage cost or prune very old objects deliberately. **CI needs the right permissions.** The pipeline identity does not need broad S3 or ECR rights of its own; it needs to be able to assume the bootstrap publishing roles in the target account. If those assume-role calls fail you get a publish error long before any infrastructure changes — which is the safe failure. **Dev-loop shortcuts skip all of this.** `cdk deploy --hotswap` detects changes it can apply by calling the service API directly — updating Lambda function code, for instance — and bypasses CloudFormation entirely for speed. It is explicitly for development: it leaves the deployed reality out of step with what CloudFormation last recorded, so a later normal deploy has to reconcile it, and it must never be used against production.

  • Why does the CDK give each asset a hash-named S3 key instead of overwriting one fixed object?
    Because CloudFormation reacts to template changes, not to bucket contents. If the key were constant, uploading new bytes would leave the template identical and CloudFormation would see nothing to update, so the function would keep running the old code. Hash-named, immutable keys make new content show up as a changed property, which is what triggers the update.
  • A CI job runs cdk synth successfully but cdk deploy fails on the container image asset. What would you check first?
    Whether the runner can actually build images — a Docker daemon available to the job, enough disk, and the right platform. Synthesis only records the image asset; the build and push happen at deploy time. After that, check that the job can assume the bootstrap image-publishing role in the target account and that the environment was bootstrapped at a version that provisions the ECR repository.
  • What is `cdk deploy --hotswap` for, and why is it development-only?
    It short-circuits CloudFormation for a few change types it recognises — most usefully Lambda function code — by calling the service API directly, turning a multi-minute deploy into seconds. The cost is that the deployed reality no longer matches what CloudFormation last recorded for the stack, so it is unsafe for shared or production environments and belongs in the local dev loop.

saying these in an interview costs you the question

  • Thinks the Lambda zip is embedded in the template
  • Assumes CloudFormation pulls files from cdk.out at deploy time
  • Believes the CDK overwrites one fixed S3 object per function
  • Expects image assets to build without a Docker daemon
  • Says the staging bucket cleans itself up automatically

context