CDK (Cloud Development Kit) — DVA-C02 Mastery Guide
- Sources: course chapter 19 (CDK). The course serves as the syllabus only; all wording, examples and questions here are original.
- Written 2026-10-12; values checked against AWS docs on 2026-10-12 (AWS CDK v2 Developer Guide, CDK API Reference, CloudFormation and SAM guides). The marker
⚠️ Changed: <current>. Older material may say <old>.flags a value or behaviour that differs from the course or older practice tests: learn the current value.⚠️ unverifiedmarks a value that could not be confirmed; (volatile) marks a value AWS changes often. Every change is listed in Changed vs older material; rows you confirm are added tonotes/current-facts.md.- Tier 2 (deployment and messaging), bank target 30 questions. Domain: Deployment.
- How to use it:
/explain <topic>teaches from these sections (concept → exam facts → developer angle → traps → 3 check questions);/quiz cdkasks bank questions whosesource_notelinks point at the## N.headings below;/drill cdkfires short recall prompts fromnotes/cdk-cheat-sheet.md. Suggested loop: read a section and answer its practice questions before opening the answers →/explainwhat is still unclear →/quiz cdk→/drill cdk.
How CDK (Cloud Development Kit) appears on DVA-C02
DVA-C02 treats the CDK as one of the ways a developer packages and ships an application, next to SAM and plain CloudFormation. It does not test framework internals; it asks whether you know what each command produces, what must exist in the account first, and which construct or setting meets a stated constraint:
- Artifacts: what
cdk synthproduces (a CloudFormation template per stack plus assets in the cloud assembly) and how Lambda code, Docker images and files reach AWS. - Deploying: the right command for each job (
bootstrap,diff,deploy,destroy), what bootstrapping creates, the error you get without it, and how CI deployments avoid interactive prompts. - Constructs: recognising L1, L2 and L3 constructs and picking the level that gives the least code or the most control.
- Testing: assertions tests on the synthesized template (fine-grained vs snapshot) and local Lambda testing with the SAM CLI.
- Configuration: environments (account + Region), context values and lookups, and the removal policies that decide whether data survives
cdk destroy. - Choosing the tool: CDK vs SAM vs hand-written CloudFormation under a constraint such as "existing YAML skills", "many non-serverless services" or "reusable typed components".
Typical question shapes: a scenario with one deciding constraint; an error message to diagnose (SSM parameter /cdk-bootstrap/hnb659fds/version not found, an export that "is in use"); "Which TWO statements…" with five options; the exact command, construct class or method that does the job.
Domain map (Deployment): prepare application artifacts → §1, §6, §9 · test in development environments → §10, §12, §13 · automate deployment testing → §9, §12 · deploy with CI/CD services → §8, §9. The CDK also appears inside Security questions (least-privilege grants §4, bootstrap trust §8) and Troubleshooting questions (logical-ID replacement §3, bootstrap errors §8).
Out of scope here (owned by other modules): CloudFormation template sections, change sets, StackSets, drift and custom resources in depth → see notes/cloudformation-guide.md; SAM templates, sam build/deploy/sync and CodeDeploy traffic shifting → see notes/sam-guide.md; CodePipeline and CodeBuild → see notes/cicd-guide.md; Lambda settings and limits → see notes/lambda-guide.md; IAM policy evaluation and iam:PassRole → see notes/advanced-identity-guide.md; the CLI credential chain and profiles → see notes/ec2-mastery-guide.md §1 and notes/cli-sdk-policies-guide.md. No OS administration or network design.
| # | Topic | Why it is on the exam |
|---|---|---|
| 1 | CDK fundamentals and the synthesis model | Code becomes CloudFormation; install, packaging, versions, tokens |
| 2 | Constructs and the three construct levels | Identify L1 / L2 / L3 and choose the level for "least code" or "full control" |
| 3 | Apps stacks and logical IDs | Stacks map to CloudFormation stacks; renaming a construct can replace a resource |
| 4 | Grants and IAM permissions | Least privilege with one method call; security approval of IAM changes |
| 5 | Escape hatches aspects and tags | Reach CloudFormation features the L2 lacks; enforce rules across an app |
| 6 | Assets and Lambda bundling | How code and images are packaged and published at deploy time |
| 7 | Removal policies and stateful resources | Why buckets and tables survive cdk destroy, and how to change that |
| 8 | Bootstrapping environments | What CDKToolkit contains, per-environment rule, trust for pipelines, bootstrap errors |
| 9 | The CDK CLI workflow | init, synth, diff, deploy, destroy and the options exam stems use |
| 10 | Hotswap and watch for fast iteration | Fast dev loops and why they never belong in production |
| 11 | Environments and context | Account and Region per stack, lookups, cdk.context.json, feature flags |
| 12 | Testing with the assertions module | Fine-grained vs snapshot tests on the synthesized template |
| 13 | CDK vs SAM and local testing with the SAM CLI | Choosing the tool; cdk synth then sam local invoke |
1. CDK fundamentals and the synthesis model
Concept
- The AWS CDK is an open-source framework for describing infrastructure in a general-purpose programming language. Your program builds an in-memory tree of constructs; the CDK then synthesizes that tree into one CloudFormation template per stack, and CloudFormation does the provisioning (change sets, ordering, rollback).
- Two halves: the AWS Construct Library (
aws-cdk-lib, with theConstructbase class in the separateconstructspackage) and the CDK Toolkit (thecdkCLI plus the programmatic CDK Toolkit Library@aws-cdk/toolkit-lib). - What a developer gains over hand-written templates: loops, conditions, classes and type checking; secure defaults and generated IAM glue; infrastructure, application code and unit tests in one repository. One app can carry Lambda handler code and container images for ECS or EKS together with the infrastructure around them.
- Synthesis happens before deployment: your program has finished running when CloudFormation starts. Values that exist only after deployment (generated names, ARNs, URLs) appear in code as tokens, placeholders that CloudFormation resolves later.
Exam facts
- ⚠️ Changed: supported languages are TypeScript, JavaScript, Python, Java, C#/.NET and Go, all rated stable. Older material may say JavaScript/TypeScript, Python, Java and .NET only.
- ⚠️ Changed: install the CLI with
npm install -g aws-cdk(a Node.js package: every CDK developer needs Node.js, currently 22.x or later (volatile), whatever language the app uses);aws-cdk-libandconstructsare per-project dependencies thatcdk initadds. Older material may saynpm install -g aws-cdk-libinstalls the CLI and libraries. - Check the installed CLI with
cdk --version. For several CDK versions on one machine, install the CLI per project (no-g) and run it withnpx aws-cdk. - CDK v2 packaging: every stable module (all L1s, the stable L2/L3 constructs and core classes such as
AppandStack) lives in the singleaws-cdk-libpackage;Constructcomes fromconstructsv10; experimental L2/L3 modules ship separately as-alphapackages (for example@aws-cdk/aws-xxx-alpha); new APIs on stable modules carry aBeta1,Beta2… suffix until they are final. - CDK v1 entered maintenance on 2022-06-01 and reached end of support on 2023-06-01; v2 is the only supported major version (re-checked, unchanged).
- ⚠️ Changed: the CLI is released separately from
aws-cdk-lib(repositoryaws-cdk-cli, CLI versions on a 2.1xxx line such as 2.1002.0); each library release works with the CLI that was current at its release and with any newer CLI, so upgrading the CLI is safe while the library version is supported. Older material may say the CLI and the library share one version number. - An outdated CLI fails on a newer app with
Cloud assembly schema version mismatch: Maximum schema version supported is …, but found …→ upgrade the CLI (downgradingaws-cdk-libis not recommended). - App lifecycle during synthesis: construction (your constructors run) → preparation (Aspects are applied) → validation (
validatehooks) → synthesis (app.synth()writes the cloud assembly). Deployment is a separate CLI step. - Cloud assembly = everything needed to deploy: a template per stack, copies of file assets, Docker image build instructions and a manifest. Default directory
cdk.out(--output/-oto change);cdk deploy --app cdk.outdeploys an existing assembly without re-synthesizing. - Tokens:
bucket.bucketNameprinted during synthesis looks like${Token[TOKEN.21]}; test withToken.isUnresolved(value); don't branch on or slice tokens in code — pass them to other constructs,CfnOutput, environment variables orFn.*functions. - Every synthesized template contains an
AWS::CDK::Metadataresource for version reporting (opt out with--no-version-reportingor"versionReporting": falseincdk.json). - Construct Hub (constructs.dev): an online catalogue where anyone can search for, publish and reuse open-source CDK construct libraries.
Developer angle
npm install -g aws-cdk # the CLI
cdk --version
mkdir orders-infra && cd orders-infra
cdk init app --language typescript # bin/, lib/, test/, cdk.json, package.json; git repo if Git is installed
npm run build # optional: surface TypeScript errors before synthesis
cdk synth # runs the app; YAML to stdout, JSON templates in cdk.out/
#!/usr/bin/env node
import { App, CfnOutput, Duration, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as sqs from 'aws-cdk-lib/aws-sqs';
class OrdersStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const queue = new sqs.Queue(this, 'OrdersQueue', {
visibilityTimeout: Duration.seconds(300),
});
// queueUrl is a token here; CloudFormation supplies the real URL at deploy time
new CfnOutput(this, 'QueueUrl', { value: queue.queueUrl });
}
}
const app = new App();
new OrdersStack(app, 'OrdersStack');
app.synth();
- The CLI runs your app with the command in the
appkey ofcdk.json(TypeScript:npx ts-node --prefer-ts-exts bin/orders-infra.ts; Python:python app.py; Java:mvn -e -q compile exec:java). Runningcdkoutside the project directory fails with--app is required either in command-line, in cdk.json or in ~/.cdk.json. - Python projects: activate
.venvand runpython -m pip install -r requirements.txtbefore the firstcdk synth.
Traps
- Trap: "The CDK creates resources by calling each service's API." → Reality: it synthesizes CloudFormation and deploys through CloudFormation; only the development-only hotswap shortcut (§10) calls service APIs directly.
- Trap: "
npm install -g aws-cdk-libgives you thecdkcommand." → Reality: the CLI package isaws-cdk;aws-cdk-libis the construct library your project imports. - Trap: "A Python or Java CDK app does not need Node.js." → Reality: the CLI itself is a Node.js tool, whatever language the app is written in.
- Trap: "Reading
bucket.bucketNamein code gives the real bucket name." → Reality: during synthesis it is a token; the value exists only after CloudFormation creates the bucket. - Trap: "After upgrading
aws-cdk-lib, downgrade the CLI so the versions match." → Reality: any newer CLI works with a supported library version; the schema-mismatch error means the CLI is too old. - Trap: "New projects can still start on CDK v1 packages such as
@aws-cdk/aws-s3." → Reality: v1 reached end of support on 2023-06-01; useaws-cdk-lib.
Practice questions
-
A developer upgrades
aws-cdk-libin a team project to the newest release. Everycdk synthnow fails withCloud assembly schema version mismatchand a message that the maximum supported schema version is lower than the one found. What is the MOST appropriate fix?- A. Pin
aws-cdk-libback to the previous release inpackage.json. - B. Upgrade the CDK CLI, for example with
npm install -g aws-cdk. - C. Delete
cdk.context.jsonso cached values are recomputed during synthesis. - D. Run
cdk bootstrapagain so the environment gets a newer bootstrap template.
- A. Pin
-
A developer new to the CDK reviews how a TypeScript app becomes deployed infrastructure. Which TWO statements are correct? (Select TWO.)
- A.
cdk synthwrites a CloudFormation template for each stack into the cloud assembly directory,cdk.outby default. - B.
cdk deploycreates each resource by calling that service's API directly, without CloudFormation. - C. A generated bucket name read in CDK code during synthesis is a token that CloudFormation resolves at deploy time.
- D. The CDK app keeps running during deployment so it can react when each resource is created.
- E. A CDK v2 app installs one npm package per AWS service, such as
@aws-cdk/aws-s3.
- A.
Answers
- B. The error means the CLI cannot read the cloud assembly schema the newer library writes; any CLI newer than the library is compatible. Pinning the library back works but is the discouraged direction and loses the upgrade; deleting the context cache and re-bootstrapping do not touch the schema version.
- A, C. Synthesis produces templates (and asset copies) in
cdk.out, and deploy-time values are tokens. Deployments go through CloudFormation, the app exits before CloudFormation starts (deploy-time code needs a custom resource), and v2 ships all stable modules in the singleaws-cdk-libpackage.
2. Constructs and the three construct levels
Concept
- A construct is the CDK's building block: a class that represents one or more CloudFormation resources plus their configuration. You compose constructs into bigger constructs, share them as normal libraries (npm, PyPI, Maven, NuGet, Go modules) and discover public ones on Construct Hub.
- Every construct is created with the same three arguments: scope (the parent, almost always
this/self), id (unique within that scope) and props (configuration; optional when every prop has a default). - The AWS Construct Library offers three levels of abstraction:
- L1 (CFN resources): classes named
Cfn<Resource>(CfnBucket,CfnFunction) that map one-to-one to a CloudFormation resource type, generated from CloudFormation's resource definitions. No defaults and no helpers: you set every property you need, with CloudFormation's property structure. - L2 (curated): hand-written classes (
s3.Bucket,lambda.Function,dynamodb.Table) for one primary resource with an intent-based API, sensible and secure defaults, generated glue (roles, policies, keys) and helper methods. - L3 (patterns): opinionated multi-resource architectures wired together for one use case, such as
apigateway.LambdaRestApiorecs_patterns.ApplicationLoadBalancedFargateService.
- L1 (CFN resources): classes named
- Choose L2 by default, L3 when a pattern matches your architecture exactly, L1 when no L2 exists or you need a property or behaviour the L2 does not expose.
Exam facts
- If a resource type exists in CloudFormation it exists as an L1 in
aws-cdk-lib; new resources and properties can take up to a week to appear. - L1 props mirror CloudFormation names in the language's casing (
versioningConfiguration,bucketName); in Python and Java nested values use generated property types such asCfnBucket.CorsConfigurationProperty. - L2 defaults do real work:
new s3.Bucket(this, 'Docs', { encryption: s3.BucketEncryption.KMS })with no key supplied also creates akms.Key; alambda.Functionwithout arolegets its own execution role withAWSLambdaBasicExecutionRole. - ⚠️ Changed: the lifecycle helper is
bucket.addLifecycleRule()(lower-case "c"). Older material may sayaddLifeCycleRule(). - Other typical L2 helpers:
bucket.addEventNotification(),fn.addEnvironment(),fn.addFunctionUrl(),table.grants.readWriteData()(§4), metric methods such asqueue.metricApproximateNumberOfMessagesVisible(). - L3 examples:
apigateway.LambdaRestApi= a REST API with Lambda proxy integration (handlerrequired;proxydefaults totrue: a greedy{proxy+}resource with anANYmethod sends every request to the function);ecs_patterns.ApplicationLoadBalancedFargateService= Fargate service on an ECS cluster behind an Application Load Balancer (the CDK docs' example with its VPC and cluster synthesizes more than 50 resources). - ⚠️ Changed: the pattern class is
ApplicationLoadBalancedFargateService("Balanced"). Older material may sayApplicationLoadBalancerFargateService, a class that does not exist. - Experimental L2/L3 constructs live in separate
-alphapackages until declared stable, then move intoaws-cdk-lib. - ⚠️ Changed: Mixins add L2-style features to L1 and L2 constructs with
.with()(for examplenew s3.CfnBucket(this, 'B').with(new s3.mixins.BucketVersioning())) or to every matching construct in a scope withMixins.of(scope).apply(…); unsupported targets are skipped silently unless you callrequireAll()/requireAny(). Facades such as Grants classes give L1s permissions helpers (§4). Older material may say L1 constructs can only be configured property by property, with no way to borrow L2 features. - AWS Solutions Constructs are vetted, AWS-published multi-service patterns you can add like any construct library.
Developer angle
import { Duration, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigw from 'aws-cdk-lib/aws-apigateway';
export class LevelsStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// L1: CloudFormation-shaped; nothing is set unless you set it
new s3.CfnBucket(this, 'RawBucket', {
versioningConfiguration: { status: 'Enabled' },
});
// L2: intent-based props, secure defaults and helper methods
const reports = new s3.Bucket(this, 'Reports', {
versioned: true,
encryption: s3.BucketEncryption.S3_MANAGED,
enforceSSL: true,
});
reports.addLifecycleRule({ expiration: Duration.days(90) });
// L3: REST API + Lambda proxy integration + invoke permissions in one construct
const handler = new lambda.Function(this, 'ApiHandler', {
runtime: lambda.Runtime.NODEJS_24_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('lambda'),
});
new apigw.LambdaRestApi(this, 'Api', { handler });
}
}
- Read the generated CloudFormation with
cdk synth LevelsStackto see how much each level adds (the L3 brings the API, deployment, stage, methods and thelambda:InvokeFunctionpermission). - Python uses the same classes with snake_case props:
s3.Bucket(self, "Reports", versioned=True, enforce_ssl=True).
Traps
- Trap: "An L2 construct is one that creates several resources." → Reality: an L2 models one primary resource (it may add glue such as a role or key); multi-resource architectures are L3 patterns.
- Trap: "L1 constructs come with secure defaults." → Reality: L1s apply no defaults at all; what you don't set is CloudFormation's default.
- Trap: "A property released in CloudFormation last week needs a new L2 release before you can use it." → Reality: use the L1 or an override on the L2's underlying L1 (§5).
- Trap: "Experimental constructs ship inside
aws-cdk-lib." → Reality: they ship as separate-alphapackages;Beta1-style suffixes are for new APIs inside stable modules. - Trap: "Construct IDs must be unique across the whole app." → Reality: only within their scope; the path from the root makes them unique.
- Trap: "A Mixin that doesn't fit the construct raises an error." → Reality: it is skipped silently unless you require it with
requireAll()/requireAny().
Practice questions
-
A developer must expose one Lambda function through an Amazon API Gateway REST API that forwards every path and method to the function. Which CDK construct meets the requirement with the LEAST code?
- A.
apigateway.CfnRestApitogether withCfnResource,CfnMethodandCfnDeploymentconstructs. - B.
apigateway.RestApi, callingaddResource()andaddMethod()for each path. - C.
apigateway.LambdaRestApiwith the function passed ashandler. - D.
fn.addFunctionUrl()withauthTypeset toFunctionUrlAuthType.NONE.
- A.
-
A developer reviews a pull request that mixes
s3.CfnBucket,s3.Bucketandecs_patternsconstructs. Which TWO statements are correct? (Select TWO.)- A. L3 constructs are published only on Construct Hub and are never part of
aws-cdk-lib. - B. Classes prefixed with
Cfnmap one-to-one to a CloudFormation resource type and apply no defaults. - C. L1 constructs are hand-written, so a new CloudFormation resource type is usable only after an L2 exists.
- D.
s3.Bucketcan create supporting resources, such as a KMS key when KMS encryption is requested without a key. - E. Experimental L2 constructs ship inside
aws-cdk-libwith aBeta1suffix on the class name.
- A. L3 constructs are published only on Construct Hub and are never part of
Answers
- C. The L3 pattern creates the REST API, a proxy resource, the integration and the invoke permission from one
handlerprop. The L1 route needs every resource spelled out;RestApineeds explicit resources and methods; a function URL is not an API Gateway REST API. - B, D.
Cfn*classes are the generated L1 layer with no defaults, and L2s generate glue such as keys. Stable L3 patterns (for exampleaws-ecs-patterns) are part ofaws-cdk-lib; L1s are generated, not hand-written; experimental modules are separate-alphapackages.
3. Apps stacks and logical IDs
Concept
- The App is the root construct (the only valid root). It contains stacks; each
Stackis the unit of deployment and becomes one CloudFormation stack in one environment. Resource constructs must live inside a stack. A Stage groups stacks that are deployed together (CDK Pipelines deploys stages). - Constructs form the construct tree; every construct exposes
node(node.id,node.path,node.children,node.scope). IDs only need to be unique within their scope. - CloudFormation tracks resources by logical ID, and the CDK derives each logical ID from the construct's path. As long as the path stays the same, the resource stays the same. Rename a construct ID or move the construct to another scope and the logical ID changes: CloudFormation creates a new resource and deletes the old one — a data-loss risk for tables, buckets and databases.
- Stacks in the same app can pass constructs to each other; the CDK wires the reference and the deployment order for you.
Exam facts
- Logical ID format: human-readable path components (without the stack) + an 8-character hash of the full path, at most 255 characters (human-readable part capped at 240). An L1 created directly in a stack keeps its plain ID; an L2's resource gets the hash (
myBucket5AF9C99B). - The child ID
Defaultis removed from both the readable part and the hash: wrap an existing resource in a new construct, give the inner resource the IDDefaultand the wrapper the resource's old ID, and the logical ID is unchanged. The IDResource(used by the L1 inside every L2) is hidden from the readable part but kept in the hash. cfnResource.overrideLogicalId('LegacyTable')pins a logical ID explicitly (useful when adopting templates).- ⚠️ Changed:
cdk refactor --unstable=refactor(preview) detects renamed or moved constructs and uses CloudFormation stack refactoring to keep the deployed resources under their new logical IDs (--dry-runto preview,--override-filefor ambiguous mappings). Older material may say a changed logical ID always means replacement unless you restore the old ID. - Stack names: a top-level stack is named after its construct ID unless you set
stackName; stacks created inside other constructs get path-derived names such asbetacpDA8372D3;cdk lslists the stack IDs you pass to commands. - Physical names: leave them to CloudFormation (recommended). An explicit name (
bucketName,tableName) makes any update that needs replacement fail, and the only way out is deleting the stack. Cross-environment references need a physical name: usePhysicalName.GENERATE_IF_NEEDED. - Cross-stack references (same app): pass the construct object through props; in the same environment the CDK writes an export in the producer and
Fn::ImportValuein the consumer — a strong reference, the default. - Strong references can deadlock: removing a shared resource fails with
Export <stack>:ExportsOutput… cannot be deleted as it is in use by <consumer>. Fix: keep the export for one deployment withthis.exportValue(…), deploy both stacks, then remove it. - ⚠️ Changed: references can be made weak — the consumer reads the producer's output with
Fn::GetStackOutput(no export lock, works across accounts and Regions) — app-wide with the context key@aws-cdk/core:defaultCrossStackReferences: "weak", per resource withCrossStackReferences.of(resource).produce(ReferenceStrength.WEAK), migrating throughBOTH. Older material may say CDK cross-stack references always use exports and that cross-Region references needcrossRegionReferences: truewith custom resources. - Dependencies: the CDK infers stack order from references; add one explicitly with
stackB.addDependency(stackA).cdk deploy StackBalso deploys the stacks it depends on unless you pass--exclusively(-e). - Nested stacks (
NestedStack): each counts as one resource in its parent and can hold up to 500 resources itself — the usual way around CloudFormation's 500-resources-per-stack limit; they are not listed bycdk list, cannot be deployed on their own, and their security changes are not shown in the deploy approval prompt. CloudFormation mechanics → seenotes/cloudformation-guide.md#5-exports-and-nested-stacks. CfnParameterworks but is discouraged: parameter values exist only at deploy time, so code cannot branch on them. Prefer props and context (§11). If you use them:cdk deploy --parameters MyStack:Name=value.new CfnOutput(this, 'ApiUrl', { value: api.url })creates stack outputs;cdk deploy --outputs-file outputs.jsonsaves them as JSON keyed by stack.
Developer angle
import { App, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as lambda from 'aws-cdk-lib/aws-lambda';
class DataStack extends Stack {
public readonly table: dynamodb.Table;
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
this.table = new dynamodb.Table(this, 'Orders', {
partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
});
}
}
interface ApiStackProps extends StackProps {
table: dynamodb.ITable;
}
class ApiStack extends Stack {
constructor(scope: Construct, id: string, props: ApiStackProps) {
super(scope, id, props);
new lambda.Function(this, 'ReadOrders', {
runtime: lambda.Runtime.NODEJS_24_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('lambda'),
environment: { TABLE_NAME: props.table.tableName }, // export + Fn::ImportValue
});
}
}
const app = new App();
const env = { account: '111122223333', region: 'eu-west-1' };
const data = new DataStack(app, 'DataStack', { env });
new ApiStack(app, 'ApiStack', { env, table: data.table }); // dependency inferred
Preserving a logical ID while refactoring into a construct:
class OrdersData extends Construct {
public readonly table: dynamodb.Table;
constructor(scope: Construct, id: string) {
super(scope, id);
// 'Default' is dropped from the logical ID, so the table keeps its identity
this.table = new dynamodb.Table(this, 'Default', {
partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
});
}
}
// in the stack: new OrdersData(this, 'Orders'); // same ID the table used before
- Check before deploying:
cdk diff ApiStack(a[-]line for the old logical ID plus a[+]line for the new one = replacement);cdk deploy ApiStack --exclusivelyskipsDataStackeven thoughApiStackdepends on it.
Traps
- Trap: "Renaming a construct ID is a harmless cleanup." → Reality: the logical ID changes and CloudFormation replaces the resource; check
cdk difffirst, useDefault,overrideLogicalId()orcdk refactor. - Trap: "Setting
bucketNamemakes deployments more predictable." → Reality: explicit physical names make replacement updates fail and block deploying the stack twice in one account. - Trap: "
cdk deploy ApiStackdeploys only that stack." → Reality: it also deploys the stacks it depends on unless you add--exclusively. - Trap: "A nested stack can be deployed or listed on its own." → Reality: nested stacks are part of their parent's deployment;
cdk listdoes not show them. - Trap: "Deleting a shared resource from the producer stack deploys cleanly once the consumer stopped using it in code." → Reality: the deployed consumer still imports the export; keep it one more deployment (
exportValue) or use weak references. - Trap: "Use
CfnParametervalues in anifstatement." → Reality: they are tokens resolved at deploy time; use props or context for synth-time decisions.
Practice questions
-
A developer moves a DynamoDB table, previously created in the stack with the construct ID
Orders, into a new reusable construct.cdk diffnow shows the table being destroyed and a new one created. Which change keeps the existing table with the FEWEST code changes?- A. Create the wrapper construct with the ID
Ordersand give the table the IDDefaultinside it. - B. Set
removalPolicy: RemovalPolicy.RETAINon the table in its new location. - C. Set
tableNameto the current physical table name in the new construct. - D. Deploy with
cdk deploy --no-rollbackso the existing table is left in place.
- A. Create the wrapper construct with the ID
-
A team splits a CDK app into
NetworkStackandServiceStackin the same account and Region;ServiceStackreceives the VPC object through its props. Which TWO statements describe what the CDK does? (Select TWO.)- A. It synthesizes both stacks into a single CloudFormation template.
- B. It requires
crossRegionReferences: trueon both stacks before the reference can synthesize. - C. It creates an export in
NetworkStackand anFn::ImportValueinServiceStack. - D. It records a dependency, so
cdk deploy ServiceStackalso deploysNetworkStackunless--exclusivelyis given. - E. It copies the VPC definition into
ServiceStack, creating a second VPC.
Answers
- A.
Defaultis removed from the logical-ID computation, soOrders/Defaultproduces the same logical ID as the oldOrderstable. Retaining would orphan the old table and still create a new empty one; an explicit name makes the replacement fail on a name conflict; disabling rollback does not change the planned replacement. - C, D. Same-environment references become an export plus
Fn::ImportValue, and the reference adds a deployment dependency. Each stack keeps its own template,crossRegionReferencesis only for references across Regions, and the VPC is referenced, not duplicated.
4. Grants and IAM permissions
Concept
- L2 constructs handle IAM idiomatically. A construct that needs a role (Lambda function, CodeBuild project, state machine) creates one that trusts the right service principal unless you pass your own, and resources offer grant helpers that add exactly the actions a use case needs to the principal's policy — plus the resource policy or KMS key permissions when those are required.
- Anything that has a role implements
IGrantable, so you grant directly to a function or project instead of to its role. - Resources imported by name or ARN (
Bucket.fromBucketName,Role.fromRoleArn) are references, not managed resources: the CDK never changes their own configuration, but they can still be named in your grants and policies. An imported role ismutableby default, so grants attach a new policy to it; with{ mutable: false }those grants are dropped (or moved to the resource side withaddGrantsToResources: true). - By default you see IAM changes before they happen:
cdk diffprints IAM statement and policy tables, andcdk deployasks for approval when a change broadens permissions.
Exam facts
- ⚠️ Changed: L2s such as
s3.Bucketanddynamodb.Tableexpose agrantsproperty —bucket.grants.read(fn),table.grants.readWriteData(fn)— and L1s get the same through Grants classes (s3.BucketGrants.fromBucket(cfnBucket).read(role)); the olderbucket.grantRead()/table.grantReadWriteData()still work but are marked discouraged in the API reference. Older material may saybucket.grantRead(fn)is the standard way to grant access. - Not every construct has moved:
fn.grantInvoke(principal)is still the documented way to let a principal invoke a Lambda function. - A bucket read grant covers S3 read actions on the bucket and its objects and, when the bucket uses a KMS key, permission to decrypt with that key; table grants cover the listed DynamoDB data actions plus the customer managed key if one is configured. Hand-written statements often forget the KMS part.
- Grants return an
iam.Grant; when a custom resource must have the permission during deployment, addcustom.node.addDependency(grant). - Add your own statements with
fn.addToRolePolicy(new iam.PolicyStatement({...}))(no-op on imported constructs) orrole.addToPolicy(...)(goes into the role's default policy); add resource-policy statements withbucket.addToResourcePolicy(...)(every such statement needs a principal). lambda.Functionwithout aroleprop gets a unique execution role with theAWSLambdaBasicExecutionRolemanaged policy.- Principal classes:
ServicePrincipal,AccountPrincipal,ArnPrincipal,OrganizationPrincipal,FederatedPrincipal,CompositePrincipal; existing IAM objects:Role.fromRoleArn(),Role.fromRoleName(),ManagedPolicy.fromAwsManagedPolicyName(). cdk deploy --require-approval broadeningis the default: the CLI stops for a y/n answer when IAM permissions or security group rules broaden. Values:never,any-change,broadening.cdk diff --security-onlyshows only broadened security changes. Changes inside nested stacks are not shown in this prompt.- Deployment permissions are separate from runtime permissions: by default your identity assumes the bootstrap roles and CloudFormation runs with the CloudFormation execution role (§8).
Developer angle
The course's image pipeline (upload to S3 → Lambda analyses the image → results stored in DynamoDB), wired with grants:
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as s3n from 'aws-cdk-lib/aws-s3-notifications';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as iam from 'aws-cdk-lib/aws-iam';
export class ImageStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const uploads = new s3.Bucket(this, 'Uploads', { encryption: s3.BucketEncryption.KMS });
const results = new dynamodb.Table(this, 'Results', {
partitionKey: { name: 'imageId', type: dynamodb.AttributeType.STRING },
});
const analyzer = new lambda.Function(this, 'Analyzer', {
runtime: lambda.Runtime.PYTHON_3_12,
handler: 'app.handler',
code: lambda.Code.fromAsset('functions/analyzer'),
environment: { TABLE_NAME: results.tableName },
});
uploads.grants.read(analyzer); // S3 read actions + kms:Decrypt on the bucket key
results.grants.readWriteData(analyzer); // DynamoDB data actions on this table only
analyzer.addToRolePolicy(new iam.PolicyStatement({
actions: ['ssm:GetParameter'],
resources: [`arn:aws:ssm:${this.region}:${this.account}:parameter/images/*`],
}));
uploads.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.LambdaDestination(analyzer));
}
}
cdk diff ImageStack --security-only # only broadened IAM / security group changes
cdk deploy ImageStack --require-approval any-change
- A role owned by another team:
iam.Role.fromRoleArn(this, 'Shared', 'arn:aws:iam::111122223333:role/shared-processing', { mutable: false })— grants then leave that role untouched. - IAM details (policy evaluation,
iam:PassRole) → seenotes/advanced-identity-guide.md#13-managed-vs-inline-policies-and-passing-roles.
Traps
- Trap: "Write an
s3:*statement on*to be safe." → Reality: a grant gives the minimal actions on that one resource (and the KMS permissions), which is what "least privilege" stems want. - Trap: "Reading a KMS-encrypted bucket needs only
s3:GetObject." → Reality: the key must allow decryption too; grants add it, hand-written statements often miss it. - Trap: "
addToRolePolicyon a function imported withfromFunctionArnadds the permission." → Reality: the imported function has noroleobject in the app, so the call does nothing. - Trap: "
bucket.grantRead()no longer exists." → Reality: it still works; current docs preferbucket.grants.read(). - Trap: "A pipeline running
cdk deploywill never stop for input." → Reality: broadened permissions trigger the approval prompt; non-interactive runs need--require-approval never(with review elsewhere). - Trap: "The approval prompt covers every stack." → Reality: security changes inside nested stacks are not displayed.
Practice questions
-
A Lambda function defined in a CDK stack must read objects from an S3 bucket in the same stack. The bucket is encrypted with a customer managed KMS key that the stack also creates. Which approach is the MOST secure and needs the least code?
- A. Attach the AWS managed policy
AmazonS3ReadOnlyAccessto the function's execution role. - B. Call
fn.addToRolePolicy()withs3:GetObjectonbucket.arnForObjects('*'). - C. Add a bucket policy that allows
s3:*for the function's execution role. - D. Call
bucket.grants.read(fn)on the bucket.
- A. Attach the AWS managed policy
-
A developer reviews the IAM behaviour of an app that defines a
lambda.Function, uses a role imported withRole.fromRoleArn(..., { mutable: false }), and deploys with the default CLI settings. Which TWO statements are correct? (Select TWO.)- A. Without a
roleprop, the function gets a dedicated execution role withAWSLambdaBasicExecutionRole. - B.
addToPolicy()on the imported role attaches the new statement to that role in the account. - C. Grant methods always change the resource policy and never the principal's identity policy.
- D.
cdk diffomits IAM changes; onlycdk driftreports them. - E.
cdk deployasks for confirmation when a change broadens IAM permissions or security group rules.
- A. Without a
Answers
- D. The grant adds S3 read actions scoped to that bucket and decrypt permission on its key. The managed policy covers every bucket and lacks KMS; the single
s3:GetObjectstatement fails on the encrypted objects withoutkms:Decrypt;s3:*is a wildcard action. - A, E. L2 functions create their own role, and
--require-approval broadeningis the default. A non-mutable imported role is not modified, grants work on the principal's policy (adding resource-policy or key permissions only where needed), andcdk diffshows IAM statement and policy tables.
5. Escape hatches aspects and tags
Concept
- When an L2 lacks something, you step down a level without leaving the CDK. In order of preference: a Mixin or Facade if one exists (§2, §4); reach the L1 inside the L2 (
node.defaultChild) and set its typed property; only if even the L1 class lacks the property (or to delete one), apply a raw override to the synthesized JSON; define aCfnResourcefor a type without aCfn*class; and, only if CloudFormation cannot do it at all, a custom resource. - You can also step up:
s3.Bucket.fromCfnBucket(cfnBucket)returns an L2-style proxy so the L1 can be passed where anIBucketis expected. - Aspects apply an operation to every construct in a scope during the preparation phase (visitor pattern): add properties, or validate and fail synthesis. Tags are implemented as aspects.
Exam facts
const cfnBucket = bucket.node.defaultChild as s3.CfnBucket;then set typed properties (cfnBucket.analyticsConfigurations = [...]) or CloudFormation options throughcfnBucket.cfnOptions(metadata,deletionPolicy,updatePolicy,condition).- Raw overrides use CloudFormation (PascalCase) names and dot paths:
addOverride('Properties.VersioningConfiguration.Status', 'Enabled'), shortcutaddPropertyOverride('VersioningConfiguration.Status', 'Enabled'), list elements by index (Tags.0.Value), deletions withaddDeletionOverride()/addPropertyDeletionOverride(). new CfnResource(this, 'Id', { type: 'AWS::Service::Thing', properties: { PascalCaseProps } })covers a resource type with no generated class yet.- Custom resources: a Lambda function handles the
Create,UpdateandDeleteevents; for a single SDK call during deployment useAwsCustomResourcefromaws-cdk-lib/custom-resources. Mechanics → seenotes/cloudformation-guide.md#10-custom-resources. - L2 proxies created with
fromCfnBucket()do not change the template; change the L1 instead. Don't wrap the same L1 in several L2 proxies. - Aspects:
Aspects.of(scope).add(new MyAspect()); the aspect implementsvisit(node: IConstruct); visits run top-down during preparation, after your code; they don't cross aStageboundary (add them on the Stage).Annotations.of(node).addError('…')makes synthesis fail and blocks deployment;--strictmakes warnings block too. - Mixins vs Aspects: Mixins apply immediately to the constructs you choose; Aspects apply at synthesis to everything in a scope, including constructs added later — use Aspects for compliance checks.
- Tags:
Tags.of(scope).add('team', 'orders')tags the scope and every taggable child;Tags.of(scope).remove('team'). Priorities: add 100 (50 for tags set directly on an L1), remove 200, higher wins; filtersincludeResourceTypes/excludeResourceTypes(exclude wins);applyToLaunchedInstances(default true) for Auto Scaling instances. Tags don't crossStageboundaries. Stack-leveltagsalso tag the CloudFormation stack itself.
Developer angle
import { Annotations, Aspects, IAspect, Stack, StackProps, Tags } from 'aws-cdk-lib';
import { Construct, IConstruct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
class RequireBucketVersioning implements IAspect {
public visit(node: IConstruct): void {
if (node instanceof s3.CfnBucket && node.versioningConfiguration === undefined) {
Annotations.of(node).addError('S3 buckets must enable versioning');
}
}
}
export class LogsStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const logs = new s3.Bucket(this, 'Logs', { versioned: true });
// escape hatch: s3.Bucket has no analytics prop, so set the typed property on the L1 inside it
const cfnLogs = logs.node.defaultChild as s3.CfnBucket;
cfnLogs.analyticsConfigurations = [{ id: 'all-objects', storageClassAnalysis: {} }];
cfnLogs.cfnOptions.metadata = { owner: 'platform-team' };
Aspects.of(this).add(new RequireBucketVersioning());
Tags.of(this).add('cost-center', 'orders');
}
}
cdk synthshows the analytics configuration and the metadata in the template; a bucket without versioning anywhere in the stack makescdk synth(and thereforecdk deploy) fail with the error annotation.- Check the L2 props first: transfer acceleration, for example, needs no escape hatch because
s3.BucketexposestransferAcceleration: true. Keep raw overrides for a property the L1 class does not model yet (addPropertyOverride()) or for removing a synthesized one (addPropertyDeletionOverride()). - Python names:
node.default_child,add_property_override,Aspects.of(self).add(...),Tags.of(self).add(...).
Traps
- Trap: "An escape hatch means editing the JSON in
cdk.out." → Reality:cdk.outis regenerated on every synthesis; overrides live in code. - Trap: "
addPropertyOverridetakes the camelCase CDK prop name." → Reality: it takes the CloudFormation property path (VersioningConfiguration.Status). - Trap: "Changing the L2 returned by
fromCfnBucket()updates the template." → Reality: it is a proxy; modify the L1. - Trap: "An Aspect runs the moment you call
Aspects.of(...).add()." → Reality: it runs during the preparation phase of synthesis; Mixins are the immediate ones. - Trap: "Tagging the App tags the stacks inside a CDK Pipelines
Stage." → Reality: tags and aspects stop atStageboundaries; apply them on the Stage. - Trap: "Any property missing from an L2 needs a custom resource." → Reality: only behaviour CloudFormation can't express needs one; a missing L2 prop is set on the L1 inside it (typed property, else a raw override).
Practice questions
-
A developer uses the
s3.BucketL2 construct and needs a property that theAWS::S3::BucketCloudFormation resource supports but the L2 does not expose. Which approach requires the FEWEST code changes?- A. Replace the bucket with an
AwsCustomResourcethat calls the S3 API after deployment. - B. Cast
bucket.node.defaultChildtos3.CfnBucketand set the property on it. - C. Export the template, edit it, and convert it back to code with
cdk migrate --from-path. - D. Edit the synthesized template in
cdk.outbefore eachcdk deploy.
- A. Replace the bucket with an
-
A platform team must make synthesis fail for any S3 bucket without versioning and tag every resource in the app's stacks with
owner=payments. Which TWO approaches meet these requirements? (Select TWO.)- A. Deploy the AWS Config managed rule
s3-bucket-versioning-enabledin each account. - B. Add a CloudFormation stack policy to every stack that denies updates to buckets without versioning.
- C. Register an Aspect with
Aspects.of(app).add()that callsAnnotations.of(node).addError()for non-compliantCfnBucketnodes. - D. Run
cdk drift --failin the pipeline before each deployment. - E. Call
Tags.of(app).add('owner', 'payments').
- A. Deploy the AWS Config managed rule
Answers
- B. The escape hatch keeps the L2 and sets the missing CloudFormation property on its L1 in one line (
addPropertyOverride()with the CloudFormation path only if even the L1 class lacks it). A custom resource is for things CloudFormation cannot express; regenerating the app withcdk migraterewrites everything as L1s;cdk deployre-synthesizes and overwrites hand edits incdk.out. - C, E. An Aspect with an error annotation fails synthesis, and
Tags.of(app)tags every taggable resource below the app. AWS Config detects non-compliance after deployment, stack policies protect resources during updates, and drift detection compares deployed resources with their templates.
6. Assets and Lambda bundling
Concept
- Assets are local files, directories or Docker images that the app needs in AWS: Lambda code, container images, static files. During synthesis the CDK records them in the cloud assembly (by default copied into
cdk.outunder a source hash); duringcdk deploythe CLI builds and publishes them — files to the bootstrap S3 bucket, images to the bootstrap ECR repository — and only then deploys the stack. - The source hash drives change detection: unchanged content → same hash → nothing re-published and no function update; changed content → new hash → new object key → CloudFormation updates the function.
- Bundling produces the deployable artifact from source:
NodejsFunctionruns esbuild, other runtimes use bundling options in a Docker build image.
Exam facts
- File assets:
lambda.Code.fromAsset('dir-or-zip')(directories are zipped); inline code withlambda.Code.fromInline(); generics3_assets.Asset(directory → zip, single file → uploaded as-is) with deploy-time attributess3BucketName,s3ObjectKey,s3ObjectUrlandasset.grantRead(principal). - Docker image assets:
ecr_assets.DockerImageAsset({ directory })(a Dockerfile must be in that directory) orecs.ContainerImage.fromAsset(dir); the CLI builds locally and pushes to ECR;buildArgspasses build arguments;asset.repository.grantPull(principal)for consumers the CDK does not wire for you. NodejsFunction(aws-cdk-lib/aws-lambda-nodejs): bundles with esbuild in your environment if esbuild is installed, otherwise inside a Lambda-compatible Docker container; withoutentryit looks for<defining-file>.<construct-id>.ts; for Node.js 18 and later runtimes the@aws-sdk/*packages are excluded from the bundle because the runtime provides SDK v3.cdk synthonly stages assets; publishing happens at deploy time (acdk diffthat creates a change set also builds and publishes them first).cdk publish-assetspublishes without deploying.- Deploy options:
--build-exclude/-E(skip rebuilding an asset),--asset-parallelism,--asset-prebuild(build all before the first stack, defaulttrue). --no-staging("staging": false) skips copying assets intocdk.outso the SAM CLI can debug the original source files (§13).- Assets need the bootstrap bucket and repository; in v2 even a stack without assets needs a bootstrapped environment, because the default synthesizer checks the bootstrap version in every template (§8).
cdk gc --unstable=gcgarbage-collects unused assets in the bootstrap bucket and repository:--type s3|ecr|all,--action full|print|tag|delete-tagged(defaultfull),--created-buffer-days(default 1),--rollback-buffer-days(default 0),--confirm(defaulttrue). From bootstrap template v24, noncurrent objects in the bootstrap bucket are kept 30 days (previously 365).- Pass deploy-time names to runtime code through environment variables (
TABLE_NAME: table.tableName); never hard-code generated names. Lambda package size limits → seenotes/lambda-guide.md#8-deployment-packages-with-layers-and-container-images.
Developer angle
import * as path from 'path';
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import * as ecs from 'aws-cdk-lib/aws-ecs';
export class AssetsStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// directory asset: zipped and uploaded to the bootstrap bucket during cdk deploy
new lambda.Function(this, 'Resize', {
runtime: lambda.Runtime.PYTHON_3_12,
handler: 'app.handler',
code: lambda.Code.fromAsset(path.join(__dirname, '../functions/resize')),
});
// bundled asset: esbuild (local, or in Docker if esbuild is missing) builds one file
new NodejsFunction(this, 'Api', {
entry: path.join(__dirname, '../functions/api/index.ts'),
runtime: lambda.Runtime.NODEJS_24_X,
});
// Docker image asset: built locally, pushed to the bootstrap ECR repository
const taskDef = new ecs.FargateTaskDefinition(this, 'TaskDef', { cpu: 512, memoryLimitMiB: 1024 });
taskDef.addContainer('web', {
image: ecs.ContainerImage.fromAsset(path.join(__dirname, '../web')),
});
}
}
npm install --save-dev esbuild # NodejsFunction bundles locally, no Docker needed
cdk deploy AssetsStack # build → publish assets → CloudFormation deploy
cdk gc --unstable=gc --type=s3 --action=print # count unused assets without deleting anything
Traps
- Trap: "
cdk synthuploads the Lambda zip to S3." → Reality: synthesis stages assets locally;cdk deploypublishes them. - Trap: "Every deployment re-uploads and updates every function." → Reality: identical content keeps its hash, so nothing changes.
- Trap: "
NodejsFunctionnever needs Docker." → Reality: it falls back to Docker when esbuild is not installed locally. - Trap: "Docker image assets are stored in the S3 staging bucket." → Reality: images go to the bootstrap ECR repository; files go to S3.
- Trap: "Assets work in an account that was never bootstrapped." → Reality: they need the bootstrap bucket and repository.
- Trap: "Hard-code the generated table name in the handler." → Reality: pass the token through an environment variable; CloudFormation fills it in.
Practice questions
-
A CI job runs
cdk deployfor a stack whoseNodejsFunctionconstructs bundle TypeScript handlers. The build image has Node.js but no Docker daemon, and synthesis fails during bundling. Which change fixes the build with the LEAST operational overhead?- A. Switch the job to a build image with Docker installed and privileged mode enabled.
- B. Replace each
NodejsFunctionwithlambda.Code.fromInline()handlers. - C. Add
esbuildas a development dependency of the CDK project. - D. Pre-build the zip files on a developer laptop and commit them to the repository.
-
A developer's stack uses
lambda.Code.fromAsset('src')and aDockerImageAsset. Which TWO statements are correct? (Select TWO.)- A. Assets are uploaded during
cdk synth, socdk deploycan run without AWS credentials. - B. During
cdk deploy, the CLI uploads the zipped directory to the bootstrap S3 bucket and pushes the image to the bootstrap ECR repository before CloudFormation updates the stack. - C. If the content of
srcis unchanged, its source hash stays the same and the function code is not updated. - D. Docker image assets are saved as tar archives in the bootstrap S3 bucket.
- E. Assets deploy to an environment that was never bootstrapped as long as the stack has fewer than 500 resources.
- A. Assets are uploaded during
Answers
- C. With esbuild available locally,
NodejsFunctionbundles without Docker. A privileged Docker build image works but adds configuration and security exposure; inline code loses bundling and dependencies; committed build artifacts drift from the source. - B, C. Publishing happens at deploy time to the bucket (files) and repository (images), and unchanged content keeps its hash. Synthesis does not upload, images live in ECR, and assets always need the bootstrap resources.
7. Removal policies and stateful resources
Concept
- A removal policy says what happens to the physical resource when it is removed from the stack, replaced, or the whole stack is deleted. The CDK writes it into the template as CloudFormation's
DeletionPolicyandUpdateReplacePolicy. - For resources that hold data the CDK default is retain — the opposite of CloudFormation's own default of delete.
cdk destroytherefore leaves buckets, tables and similar resources behind, orphaned from any stack. - For throwaway environments set
RemovalPolicy.DESTROY; S3 buckets that still contain objects also needautoDeleteObjects: true, which adds a custom resource that empties the bucket before deletion.
Exam facts
RemovalPolicyvalues:RETAIN(keep, orphaned; the default for most stateful resources),DESTROY(delete with the stack),SNAPSHOT(delete but snapshot first; only some resources such as databases and EBS volumes),RETAIN_ON_UPDATE_OR_DELETE(keep on stack deletion and on replacement, but delete if the initial creation is rolled back).s3.Bucketdefault: orphaned (retained);dynamodb.Tabledefault:RemovalPolicy.RETAIN.- CloudFormation refuses to delete a bucket that still has objects, even with
DESTROY;autoDeleteObjects: truerequiresremovalPolicy: RemovalPolicy.DESTROYand adds aCustom::S3AutoDeleteObjectsresource, its Lambda function and a bucket policy statement. - Constructs without a
removalPolicyprop:resource.applyRemovalPolicy(RemovalPolicy.DESTROY); on an L1:cfnResource.cfnOptions.deletionPolicy = CfnDeletionPolicy.DELETE. - A retained resource with an explicit physical name blocks redeploying the stack (name conflict) until you delete or import it.
- Some non-data resources use
removalPolicydifferently: on a Lambda version it decides whether old versions are kept when a new one is published. cdk destroy [stacks]asksAre you sure you want to delete: <stack> (y/n)?;--force/-fskips the prompt;--all;--exclusively;--concurrency. It deletes only the app's stacks — theCDKToolkitbootstrap stack stays — and retained resources survive.cdk diffshows a policy change asDeletionPolicy/UpdateReplacePolicygoing fromRetaintoDelete.- Stack-level protection:
terminationProtection: trueon the stack props (defaultfalse). DeletionPolicy semantics → seenotes/cloudformation-guide.md#9-protecting-resources-and-stacks.
Developer angle
import { RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
export class SandboxStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// only an explicit dev stage deletes data; a missing or mistyped -c stage keeps it
const isDev = this.node.tryGetContext('stage') === 'dev';
new s3.Bucket(this, 'Artifacts', {
removalPolicy: isDev ? RemovalPolicy.DESTROY : RemovalPolicy.RETAIN,
autoDeleteObjects: isDev, // valid only together with DESTROY
});
new dynamodb.Table(this, 'Sessions', {
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
removalPolicy: isDev ? RemovalPolicy.DESTROY : RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE,
});
}
}
cdk deploy -c stage=dev SandboxStack
cdk destroy SandboxStack --force # bucket emptied and deleted, table deleted
cdk destroy --all # every stack of the app, after a y/n confirmation
Traps
- Trap: "
cdk destroyremoves everything the app created." → Reality: retained resources (the default for buckets and tables) stay in the account. - Trap: "
RemovalPolicy.DESTROYis enough to delete a bucket full of objects." → Reality: CloudFormation fails on non-empty buckets; addautoDeleteObjects: true. - Trap: "
autoDeleteObjects: truedeletes the bucket by itself." → Reality: it only works together withDESTROY. - Trap: "The CDK uses CloudFormation's default (Delete)." → Reality: the CDK default for stateful resources is retain.
- Trap: "
DESTROYeverywhere keeps the account tidy." → Reality: in production it risks losing data on a replacement or stack deletion; keepRETAINorSNAPSHOTthere. - Trap: "
cdk destroy --forceoverrides retain policies." → Reality:--forceonly skips the confirmation prompt.
Practice questions
-
Developers run
cdk destroyon their personal sandbox stacks every evening, yet S3 buckets (containing test files) and DynamoDB tables from those stacks keep piling up in the account. Which change removes them with the LEAST ongoing effort?- A. Add
--forceto the eveningcdk destroycommand. - B. Set
RemovalPolicy.SNAPSHOTon the buckets and tables in sandbox stacks. - C. Schedule a Lambda function that finds and deletes orphaned buckets and tables each night.
- D. In sandbox stacks, set
removalPolicy: RemovalPolicy.DESTROYon both resources andautoDeleteObjects: trueon the bucket.
- A. Add
-
A production stack defines a DynamoDB table and an S3 bucket with default settings. Which TWO statements are correct? (Select TWO.)
- A. Deleting the stack leaves the table and the bucket in the account, no longer managed by CloudFormation.
- B. The synthesized template sets
DeletionPolicy: Deleteon both resources, matching CloudFormation's default. - C. Setting
RemovalPolicy.DESTROYon the bucket alone lets CloudFormation delete it even when it contains objects. - D.
cdk destroy --forceoverrides the retain policy and deletes both data stores. - E. With
RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE, the table is kept when a property change forces replacement, but not when its initial creation is rolled back.
Answers
- D. Destroy policies plus automatic object deletion let the existing
cdk destroyremove everything.--forceonly skips the prompt; snapshots are not available for S3 buckets and would still leave data behind; a cleanup function works but is extra code to run and maintain. - A, E. Stateful resources default to retain and are orphaned on stack deletion, and
RETAIN_ON_UPDATE_OR_DELETEbehaves as described. The CDK default is the opposite of CloudFormation's, non-empty buckets also needautoDeleteObjects, and--forceonly skips confirmation.
8. Bootstrapping environments
Concept
- Bootstrapping is the initial setup of an environment (one account in one Region):
cdk bootstrapdeploys a template maintained by the CDK team as a CloudFormation stack namedCDKToolkit, holding the storage, IAM roles and version marker that every later CDK deployment there relies on. - Why it exists: assets need a staging bucket and an image repository; deployments run through dedicated IAM roles (which is what makes cross-account deployments and CDK Pipelines possible); and a version parameter lets apps demand a minimum bootstrap version.
- CDK v2 uses only the modern bootstrap template; environments bootstrapped with the v1 legacy template must be bootstrapped again.
- With the default synthesizer (
DefaultStackSynthesizer), every synthesized template checks the bootstrap version, so every environment you deploy to must be bootstrapped first, even for a stack without assets.
Exam facts
- Command:
cdk bootstrap aws://ACCOUNT-ID/REGION(theaws://prefix is optional; several environments can be listed in one command). Run inside a project without arguments, it bootstraps the environments of the app's stacks (or the default /--profileenvironment for environment-agnostic stacks). - ⚠️ Changed:
CDKToolkitcontains an S3 staging bucket (files, Lambda code, large templates), an ECR repository (Docker images), five IAM roles —CloudFormationExecutionRole,DeploymentActionRole,FilePublishingRole,ImagePublishingRole,LookupRole— and the SSM parameter/cdk-bootstrap/hnb659fds/version; current templates create no KMS key by default (environments bootstrapped with older versions can drop theirs with--no-bootstrap-customer-key). Older material may say the stack holds only an S3 bucket and IAM roles. - Physical names follow
cdk-<qualifier>-<description>-<account>-<Region>(for examplecdk-hnb659fds-assets-111122223333-eu-west-1); the default qualifierhnb659fdsis a meaningless 9-character string. Change it with--qualifieronly for name isolation (several bootstrap stacks in one account) and pass the same value to the app'sDefaultStackSynthesizer(qualifier). - ⚠️ Changed: deploying into an unbootstrapped environment fails with
SSM parameter /cdk-bootstrap/hnb659fds/version not found. Has the environment been bootstrapped? Please run 'cdk bootstrap'; the messagePolicy contains a statement with one or more invalid principalsis what a CDK Pipeline reports when the target account was not bootstrapped (its trusted roles don't exist). Older material may say the invalid-principals message is the general sign of a missing bootstrap. - The
cdk deploycommand reference still says an unbootstrapped environment accepts stacks without assets whose template is under 51,200 bytes. That is the CDK v1 rule (legacy synthesizer: bootstrap only for assets or templates over 50 KiB); with the v2 default synthesizer such a stack also fails on the bootstrap version check, so bootstrap first. - Synthesized templates carry a
BootstrapVersionparameter (read from SSM) and aCheckBootstrapVersionrule whose failure message tells you to runcdk bootstrapwith a recent CLI. - Re-running
cdk bootstrapis safe: it upgrades the stack if a newer template exists, otherwise does nothing. Never delete and recreateCDKToolkit(pipelines break with no general recovery); protect it with--termination-protection. - The template is versioned: v32 at CLI 2.1120.0 (volatile);
cdk rollbackneeds v23 or later,cdk importv12 or later; v24 shortened noncurrent-object retention in the bucket from 365 to 30 days. --cloudformation-execution-policies <ARN,ARN>: managed policies for the CloudFormation execution role; defaultAdministratorAccess; mandatory when you use--trust.--trust <account>: other accounts allowed to deploy into this environment (the bootstrapping account is always trusted). When updating, list every account again — accounts left out lose trust. Trusted accounts effectively get the execution policies' permissions in this account.--trust-for-lookup <account>: lookups only (synthesis), no deployments.- Other options:
--bootstrap-bucket-name,--bootstrap-kms-key-id(existing key) or--bootstrap-customer-key(new customer managed key, charged per environment),--tags KEY=VALUE,--toolkit-stack-name,--show-template,--template <file>; custom templates should change theBootstrapVariantparameter so a defaultcdk bootstrapcannot overwrite them. - Organisation scale:
cdk bootstrap --show-template > bootstrap-template.yaml, then deploy it with CloudFormation StackSets, AWS Control Tower oraws cloudformation create-stack --stack-name CDKToolkit --capabilities CAPABILITY_NAMED_IAM. - The identity that bootstraps needs at least
cloudformation:*,ecr:*,ssm:*,s3:*andiam:*. - Default deployment flow: your identity assumes
DeploymentActionRole, which passesCloudFormationExecutionRoleto CloudFormation (the execution role's policies decide what the stack may create);FilePublishingRoleandImagePublishingRoleupload assets;LookupRole(read-only) serves context lookups, also during synthesis. Both the CLI and CloudFormation read the bootstrap version parameter. - CDK Pipelines: bootstrap the pipeline account with
--cloudformation-execution-policies, and each target account with--trust <pipeline-account>plus execution policies. CodePipeline itself → seenotes/cicd-guide.md.
Developer angle
cdk bootstrap aws://111122223333/eu-west-1 --profile dev
cdk bootstrap aws://111122223333/eu-west-1 aws://111122223333/us-east-1 # two Regions at once
# production account deployed by a pipeline in the tools account 111122223333
cdk bootstrap aws://444455556666/eu-west-1 --profile prod-admin \
--trust 111122223333 \
--cloudformation-execution-policies "arn:aws:iam::444455556666:policy/OrdersDeployPolicy" \
--termination-protection
cdk bootstrap --show-template > bootstrap-template.yaml # inspect or customise
aws ssm get-parameter --name /cdk-bootstrap/hnb659fds/version --query Parameter.Value --output text
aws cloudformation describe-stacks --stack-name CDKToolkit --query "Stacks[0].Outputs"
A deployer identity in CI mainly needs to assume the bootstrap roles (the CloudFormation execution role does the resource work):
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::111122223333:role/cdk-hnb659fds-*"
}]
}
Traps
- Trap: "Bootstrap once per account." → Reality: once per account and Region; every new Region needs its own
CDKToolkit. - Trap: "Only stacks with Lambda code or images need bootstrapping." → Reality: that was v1 behaviour; v2's default synthesizer checks the bootstrap version for every stack.
- Trap: "Re-running
cdk bootstrapbreaks existing deployments." → Reality: it upgrades the stack in place or does nothing. - Trap: "Delete
CDKToolkitto fix a broken bootstrap." → Reality: deleting it breaks deployments and pipelines; update it by bootstrapping again. - Trap: "
--trustalone lets the pipeline account deploy." → Reality: you must also pass--cloudformation-execution-policies, and re-list previously trusted accounts. - Trap: "The default execution role is least privilege." → Reality: it is
AdministratorAccessunless you pass other policies. - Trap: "A new qualifier isolates permissions between teams." → Reality: it only isolates names; permissions isolation needs scoped execution policies.
Practice questions
-
A developer runs
cdk deployagainst a Region the team has never used and getsSSM parameter /cdk-bootstrap/hnb659fds/version not found. The same app deploys without errors to another Region in the same account. What should the developer do?- A. Run
cdk bootstrap aws://<account-id>/<new-region>. - B. Create the SSM parameter manually with a value of
6in the new Region. - C. Delete
cdk.context.jsonand runcdk deployagain. - D. Add
--role-arnwith an administrator role to thecdk deploycommand.
- A. Run
-
A company deploys CDK apps from a tools account (111122223333) with CDK Pipelines into a production account (444455556666). The security team requires that deployments into production can create only the resource types the application uses. Which TWO actions are required when bootstrapping the production account? (Select TWO.)
- A. Pass
--qualifierwith a new value to isolate the pipeline's permissions from other bootstrap stacks. - B. Pass
--trust 111122223333so the pipeline account can assume the deployment roles. - C. Pass
--trust-for-lookup 111122223333instead of--trustso that deployments use read-only roles. - D. Pass
--cloudformation-execution-policieswith a scoped managed policy instead of relying on theAdministratorAccessdefault. - E. Delete and recreate the existing
CDKToolkitstack so the new trust settings take effect.
- A. Pass
Answers
- A. The missing version parameter means the Region was never bootstrapped; bootstrapping creates it along with the bucket, repository and roles. A hand-made parameter leaves the rest missing; the context cache and an administrator role do not create bootstrap resources.
- B, D. Trust lets the pipeline account assume the deployment roles, and a scoped execution policy limits what CloudFormation can create. A new qualifier isolates names only, lookup trust cannot deploy, and re-running
cdk bootstrapupdates the stack in place.
9. The CDK CLI workflow
Concept
- The daily loop:
cdk init→ write constructs →cdk synth(validate and inspect) →cdk diff(preview against what is deployed) →cdk deploy→ iterate →cdk destroywhen the environment is no longer needed. - Most commands run your app first (synthesis), so they need the
appcommand fromcdk.jsonand, for lookups, AWS credentials. - Settings come from the command line, then the project
cdk.json, then~/.cdk.json(in that order of precedence). Credentials and Region come from the same environment variables and sharedconfig/credentialsfiles as the AWS CLI (thedefaultprofile unless you pass--profile); credential chain details → seenotes/cli-sdk-policies-guide.md#3-cli-credential-provider-chain.
Exam facts
cdk init [app|lib|sample-app] --language <typescript|javascript|python|java|csharp|fsharp|go>:appis the default template;lib(construct library) is TypeScript only;sample-appcontains an SQS queue and an SNS topic; Git is initialised when available;--generate-onlyskips git, dependency install and compilation;cdk init --listshows templates and languages.cdk list/cdk ls(add--longfor names and environments).cdk synth [stack]: writes the cloud assembly; with one stack (or one named) the YAML template is also printed;--quiet/-qsuppresses printing,--json/-jprints JSON,--exclusively/-eskips dependencies,--validation(defaulttrue) runs extra template checks.- ⚠️ Changed:
cdk diffcompares the app with the deployed stack (or with a file via--template) and by default (--method auto) creates a read-only change set to report replacements accurately, falling back to a template comparison;--method templateis faster and uses the lookup role;--change-setis deprecated. Older material may saycdk diffonly compares template text. cdk diffoutput marks[+]add,[-]remove,[~]modify;--failexits with code 1 when differences exist;--security-onlylimits output to broadened security changes.cdk deploy [stacks]: synthesizes (unless--app cdk.out), publishes assets, then deploys through a change set (--method change-set, the default;directskips the change set;prepare-change-setcreates one for review andexecute-change-setruns it, default namecdk-deploy-change-set). With no changes it skips the stack unless--force.- Stack selection: one stack → implicit; otherwise names,
--all, or quoted wildcards ('*'matches top-level stacks,'**'every stack of a CDK Pipelines hierarchy). - Deploy options that stems use:
--require-approval never|any-change|broadening(defaultbroadening);--outputs-file/-O;--parameters Stack:Key=Value(--previous-parametersdefaulttrue);--concurrency N(default 1);--exclusively;--notification-arns;--progress events;--import-existing-resources;--no-rollback/-R(keep partial progress; replacement updates always fail in this mode) andcdk rollbackto return a failed or paused stack to its last stable state;--revert-drift;--express(§10). - Global options:
--app/-a,--context/-c,--profile,--role-arn/-r(role for CloudFormation interactions),--output/-o,--ci(logs to stdout),--verbose/-v,--debug,--strict,--lookups. - Other commands:
cdk import(adopt existing resources into a stack via CloudFormation resource import; model the resource exactly, no nested stacks, bootstrap v12+);cdk migrate(preview:--from-stack,--from-pathor--from-scanwith IaC generator → new app with one stack of L1 constructs;--stack-namerequired);cdk drift(CloudFormation drift detection,--failexits 1);cdk refactor(preview, §3);cdk gc(§6);cdk watch(§10);cdk context(§11);cdk orphan(detach resources from a stack without deleting them);cdk publish-assets;cdk doctor;cdk docs;cdk notices/cdk acknowledge;cdk flags;cdk metadata. - In CI the approval prompt would block: deploy with
--require-approval never(review IAM changes elsewhere, for examplecdk diffin an earlier stage) or use CDK Pipelines. - CDK Pipelines (
aws-cdk-lib/pipelines): aCodePipelineconstruct with aShellStepsynth step (npm ci,npm run build,npx cdk synth) builds a self-updating CodePipeline (selfMutation, defaulttrue; turn it off only while iterating on the pipeline itself withcdk deploy);pipeline.addStage(stage)deploys aStageof stacks, stages added this way deploy in sequence, the stages of onepipeline.addWave()wave deploy in parallel (for example one per Region), and target accounts are bootstrapped with--trust(§8). CodePipeline itself → seenotes/cicd-guide.md#10-codepipeline-stages-actions-and-artifacts.
Developer angle
cdk ls --long
cdk synth OrdersStack --quiet
cdk diff OrdersStack --fail # non-zero exit code if the deploy would change anything
cdk deploy OrdersStack --require-approval never --outputs-file outputs.json
cdk deploy --all --concurrency 3 # independent stacks in parallel
cdk deploy OrdersStack --method=prepare-change-set --change-set-name review-123
cdk deploy OrdersStack --no-rollback # iterate without waiting for rollbacks
cdk rollback OrdersStack # back to the last stable state
cdk destroy OrdersStack --force
- Team defaults live in
cdk.jsonnext to theappkey, for example"requireApproval": "broadening","outputsFile": "outputs.json","progress": "events"and a"context"object (§11).
Traps
- Trap: "
cdk synthdeploys the stack." → Reality: it only produces templates and staged assets. - Trap: "
cdk diffcompares the code with the last synthesis." → Reality: it compares with the deployed stack (or a template file you give it). - Trap: "
cdk driftandcdk diffare interchangeable." → Reality: drift compares live resources with CloudFormation's record; diff compares your code with the deployed template. - Trap: "
cdk deploy '*'deploys every stack of a CDK Pipelines app." → Reality:*matches top-level stacks only; use'**'. - Trap: "
--no-rollbackworks for any change." → Reality: updates that require replacement always fail in that mode. - Trap: "
cdk importandcdk migratedo the same thing." → Reality: import adds specific existing resources to a stack you model; migrate generates a new app (L1 constructs) from a stack, template or scan.
Practice questions
-
In a CodeBuild stage, a team wants the build to fail whenever the CDK app would change the deployed production stack, so that someone reviews the change before a separate deploy stage runs. Which command meets this requirement?
- A.
cdk synth ProdStack --quiet - B.
cdk diff ProdStack --fail - C.
cdk drift ProdStack --fail - D.
cdk deploy ProdStack --method=direct
- A.
-
A developer iterates on a development stack. Failed deployments spend several minutes rolling back before the developer can retry. The developer wants to keep the resources that were created successfully after a failure and, when needed, return the stack to its last stable state. Which TWO commands or options should the developer use? (Select TWO.)
- A.
cdk deploy --force - B.
cdk deploy --no-rollback - C.
cdk deploy --method=prepare-change-set - D.
cdk rollback DevStack - E.
cdk destroy DevStack --exclusively
- A.
Answers
- B.
--failturns any difference against the deployed stack into exit code 1. Synthesis does not compare with the account, drift detection checks out-of-band changes to live resources rather than pending code changes, and a direct deploy would apply the change instead of stopping it. - B, D.
--no-rollbackkeeps the progress of a failed deployment, andcdk rollbackreturns the paused stack to its last stable state.--forceonly redeploys unchanged stacks, a prepared change set does not deploy, and destroying the stack throws away everything.
10. Hotswap and watch for fast iteration
Concept
- For development loops the CDK can skip CloudFormation for supported changes and call the service APIs directly — a hotswap — turning a multi-minute deployment into seconds.
- The price: the deployed resource no longer matches what CloudFormation recorded (deliberate drift) and there is no rollback. Hotswap is for development stacks only.
cdk watch(=cdk deploy --watch) watches your files, rebuilds, hotswaps what it can and streams the functions' CloudWatch logs to your terminal.- CloudFormation express mode (
cdk deploy --express) is a different speed-up: it stays inside CloudFormation but reports completion as soon as each resource's configuration is applied, without waiting for stabilisation.
Exam facts
- ⚠️ Changed:
cdk deploy --hotswapapplies hotswappable changes directly and skips (with a message) any change that cannot be hotswapped;--hotswap-fallbackperforms a full CloudFormation deployment when a change needs one. Older material may say--hotswapfalls back to a CloudFormation deployment by itself; the CDK guide's CLI overview page (cli.html) still says so for--hotswapand watch mode, but thecdk deployandcdk watchcommand references (and the CLI) skip such changes. - ⚠️ Changed: hotswappable today (volatile): Lambda code assets (including container images and inline code), tags, description and environment variables; Lambda versions and aliases; Step Functions definitions; ECS container assets; S3
BucketDeploymentassets; CodeBuild source and environment; AppSync resolvers, functions and schemas; API Gateway REST and HTTP/WebSocket API changes; Amazon Bedrock agents and AgentCore Runtime; EventBridge rules; DynamoDB table configuration; SQS queue configuration; CloudWatch alarms and dashboards. Older material may say only Lambda functions, Step Functions state machines and ECS images. - Hotswap calls use your current credentials, not the bootstrap roles, so your identity needs direct permissions in the stack's account (for example
lambda:UpdateFunctionCode). - ECS hotswaps set the service's minimum healthy percent to 0; hotswap also works for resources in nested stacks.
- After hotswapping,
cdk deploy --revert-driftdeploys a drift-aware change set (REVERT_DRIFTmode) that puts drifted resources back to the template state. cdk watch: hotswap on by default (--hotswap=true), no fallback unless--hotswap-fallback;--logsdefaulttrue;--concurrencyfor several stacks; stop with Ctrl+C. Files watched come from"watch": {"include": …, "exclude": …}incdk.json(cdk initsetsincludeto**/*; dot-files,cdk.outandnode_modulesare always excluded); a"build"command runs before each synthesis.- Express mode:
cdk deploy --express/cdk destroy --express; no automatic rollback unless you add--rollback; resources may still be initialising when the command returns; the stack stays consistent with its template (unlike hotswap); not recommended for production; not supported for StackSets. - SAM's equivalent loop (
sam sync --watch) → seenotes/sam-guide.md.
Developer angle
cdk deploy DevStack --hotswap # hotswappable changes only; the rest is skipped
cdk deploy DevStack --hotswap-fallback # hotswap where possible, otherwise a full deployment
cdk watch DevStack # continuous: build, hotswap, stream logs
cdk deploy DevStack --revert-drift # reconcile drift left by hotswaps
cdk deploy DevStack --express # CloudFormation express mode (no stabilisation wait)
{
"app": "npx ts-node --prefer-ts-exts bin/orders.ts",
"build": "npm run build",
"watch": {
"include": ["bin/**", "lib/**", "functions/**"],
"exclude": ["**/*.test.ts"]
}
}
Traps
- Trap: "Use
--hotswapin the production pipeline to speed up releases." → Reality: it creates drift and has no rollback; development only. - Trap: "
--hotswapdeploys every change in the stack." → Reality: non-hotswappable changes are skipped unless you use--hotswap-fallback. - Trap: "Hotswap runs under the bootstrap deployment role." → Reality: it uses your own credentials, which need the service permissions.
- Trap: "After a hotswap the stack shows no drift." → Reality: CloudFormation's record is now stale; reconcile with a normal deployment or
--revert-drift. - Trap: "Express mode bypasses CloudFormation like hotswap." → Reality: express mode is a CloudFormation deployment that just doesn't wait for stabilisation.
Practice questions
-
A developer changes only the handler code of a Lambda function in a CDK development stack and wants the change live within seconds, without waiting for a CloudFormation stack update. Which command should the developer run?
- A.
cdk deploy DevStack --method=direct - B.
cdk deploy DevStack --express - C.
cdk deploy DevStack --hotswap - D.
cdk synth DevStack --no-staging
- A.
-
A team runs
cdk watchagainst a shared development stack. Which TWO statements are correct? (Select TWO.)- A. Hotswapped changes are recorded in the CloudFormation template, so the stack shows no drift afterwards.
- B.
cdk watchis the recommended way to promote changes to production stacks. - C.
cdk watchworks only for TypeScript CDK apps. - D. Changes that cannot be hotswapped are skipped unless
--hotswap-fallbackis used. - E. Hotswap API calls are made with the developer's own credentials instead of the bootstrap deployment role.
Answers
- C. A hotswap updates the function code through the Lambda API without a stack update.
--method=directand--expressare still CloudFormation deployments (faster ones), and synthesis deploys nothing. - D, E. Watch mode hotswaps by default, skips non-hotswappable changes without the fallback flag, and uses the caller's credentials. Hotswaps cause drift, watch mode is for development only, and it works with any CDK language because it drives the same CLI.
11. Environments and context
Concept
- An environment is the AWS account and Region a stack deploys to. A stack with
envset is environment-specific: the account and Region are fixed at synthesis. A stack withoutenvis environment-agnostic: the template uses pseudo parameters and the target is chosen at deploy time from your profile. - Context is key-value configuration that constructs read during synthesis. It carries your own settings (stage name, sizes), the CDK's feature flags, and cached results of lookups — queries against your account (existing VPCs, hosted zones, AMIs, SSM parameters) made at synthesis time and frozen in
cdk.context.jsonso later syntheses produce the same template.
Exam facts
- Set
env: { account: '111122223333', region: 'eu-west-1' }per stack; hard-coded values are the recommended choice for production. CDK_DEFAULT_ACCOUNTandCDK_DEFAULT_REGIONare set by the CLI from the profile in use (--profileor default); using them inenvlets one app target whoever runs it — convenient in development, not recommended for production because the template then depends on the machine, user or session. A common pattern prefers customCDK_DEPLOY_ACCOUNT/CDK_DEPLOY_REGIONvariables and falls back to the defaults.- Environment precedence: hard-coded
env→ environment variables used inenv→ profile passed with--profile→defaultprofile. An environment written into the template wins over a different--profileat deploy time. - Environment-agnostic stacks cannot use lookups (
Vpc.fromLookupfails because account and Region are not specified at the stack level) or branch onstack.region;stack.availabilityZonesreturns exactly two AZs so the template fits any Region; account and Region resolve throughAWS::AccountId/AWS::Regionat deploy time. - Lookup methods:
Vpc.fromLookup,HostedZone.fromLookup,StringParameter.valueFromLookup,stack.availabilityZones(environment-specific stacks),LookupMachineImage. They need an explicit account and Region and credentials at synthesis (the CLI uses the bootstrapLookupRole); missing values are fetched, written tocdk.context.jsonand the app is re-run. - Synth time vs deploy time for SSM values:
ssm.StringParameter.valueFromLookup()reads the parameter during synthesis and the value stays frozen incdk.context.jsonuntil you reset it;ssm.StringParameter.valueForStringParameter()returns a token that resolves to the parameter's current value during deployment, with no cache. - Cached keys embed account and Region (
availability-zones:account=123456789012:region=eu-central-1); never edit cached values by hand. - Commit
cdk.jsonandcdk.context.jsonto source control, or CI may synthesize different templates than developers. - Six context sources: the current AWS account (lookups),
--context/-con the command line,cdk.context.json, thecontextkey ofcdk.json, thecontextkey of~/.cdk.json, andconstruct.node.setContext()in code. --context key=value(repeat for more); command-line values are always strings and reach every stack being synthesized, so per-stack values need different keys or separate commands. Read values withthis.node.tryGetContext('key')(undefined when absent); values set by the CLI live on the App, so every construct sees them.cdk contextlists cached values,cdk context --reset <key|number>removes one (refreshed on the next synth),cdk context --clearremoves all; onlycdk.context.jsonvalues can be reset — anything incdk.jsonis untouched.--lookups=false/"lookups": falsemakes synthesis fail instead of querying AWS when a lookup is missing (useful in CI with a committed cache).- Feature flags are context values in
cdk.json:cdk initenables every flag current at that CLI version for new projects; existing projects keep the old behaviour until you add the flag;cdk context --cleardoes not remove them;cdk flagsviews and modifies them.
Developer angle
#!/usr/bin/env node
import { App } from 'aws-cdk-lib';
import { OrdersStack } from '../lib/orders-stack';
const app = new App();
const stage: string = app.node.tryGetContext('stage') ?? 'dev';
new OrdersStack(app, `Orders-${stage}`, {
env: stage === 'prod'
? { account: '444455556666', region: 'eu-west-1' } // pinned for production
: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
});
Inside the stack, a lookup that needs the explicit environment:
const vpc = ec2.Vpc.fromLookup(this, 'SharedVpc', { tags: { Name: 'shared-services' } });
cdk synth -c stage=prod # context from the command line (a string)
cdk deploy --profile sandbox # CDK_DEFAULT_* resolve from this profile
cdk context # list cached lookups in cdk.context.json
cdk context --reset 2 # forget one cached value; refreshed on next synth
cdk context --clear # forget all cached values (feature flags stay)
cdk synth --no-lookups # fail instead of querying AWS (CI with a committed cache)
Traps
- Trap: "
Vpc.fromLookupworks in any stack." → Reality: the stack needs an explicit account and Region, and synthesis needs credentials. - Trap: "
CDK_DEFAULT_ACCOUNTis the best practice for production stacks." → Reality: it ties the template to whoever runs the CLI; pin production environments. - Trap: "Add
cdk.context.jsonto.gitignore." → Reality: commit it so every synthesis uses the same cached values. - Trap: "
-c retries=3arrives in code as a number." → Reality: command-line context is always a string; convert it. - Trap: "When AWS adds an AZ or a newer AMI, the app picks it up automatically." → Reality: cached values are frozen until you reset them.
- Trap: "
cdk context --clearresets feature flags." → Reality: it only clearscdk.context.json; flags live incdk.json. - Trap: "
cdk deploy --profile prodredirects a stack whoseenvis hard-coded to another account." → Reality: the environment in the template wins.
Practice questions
-
A developer adds
ec2.Vpc.fromLookup()to a stack that has noenvproperty, andcdk synthfails with an error that the account and Region are not specified at the stack level. Which change fixes the error and keeps the synthesized template identical on every developer machine and in CI?- A. Set
envon the stack to the target account ID and Region as literal values. - B. Set
envon the stack fromprocess.env.CDK_DEFAULT_ACCOUNTandprocess.env.CDK_DEFAULT_REGION. - C. Pass
--profile prodto everycdk synthcommand. - D. Run
cdk context --clearbefore each synthesis.
- A. Set
-
A CI pipeline produces a different template than developers' laptops because the result of a
Vpc.fromLookup()call changed in the account. Which TWO practices make synthesis deterministic? (Select TWO.)- A. Add
cdk.context.jsonto.gitignoreso each environment computes fresh values. - B. Commit
cdk.context.jsonto source control with the rest of the app. - C. Run
cdk synth --no-lookupsin CI so a missing cached value fails the build instead of querying AWS. - D. Run
cdk context --clearat the start of every pipeline run. - E. Pass every looked-up value with
--contextso the CLI treats it as a number.
- A. Add
Answers
- A. Literal account and Region make the stack environment-specific, which lookups require, and the template no longer depends on who runs the CLI. The
CDK_DEFAULT_*variables also fix the error but vary with the caller's profile; a profile alone does not give an environment-agnostic stack an environment; clearing context does not help. - B, C. A committed cache freezes lookup results for everyone, and disabling lookups in CI stops silent re-queries. Ignoring or clearing the cache causes exactly the drift described, and command-line context values are strings.
12. Testing with the assertions module
Concept
- CDK infrastructure code is unit-testable: a test instantiates the stack, synthesizes it in memory and asserts on the CloudFormation template that results. Nothing is deployed.
- The tools: the
assertionsmodule ofaws-cdk-lib(aws_cdk.assertionsin Python) plus a normal test framework — Jest for TypeScript and JavaScript, pytest for Python (JUnit, MSTest and so on for other languages). - Two styles:
- Fine-grained assertions check specific facts ("a queue with this visibility timeout exists", "exactly two topics"). They are the most common, catch regressions and suit test-driven development.
- Snapshot tests save a full copy of the synthesized template once and fail whenever a later synthesis differs from that saved copy. They are handy when refactoring (any unintended change shows up) but also fail on harmless differences from CDK upgrades, feature flags or context, so they can't be your only guard.
Exam facts
- Load a template:
Template.fromStack(stack)synthesizes a CDK stack;Template.fromJSON(object)(parsed object) andTemplate.fromString(jsonString)(JSON text) wrap an existing template the CDK did not generate, such as a legacy CloudFormation file. The course mentions onlyfromStackandfromString. - Assertions on resources:
hasResourceProperties(type, props)(partial match onPropertiesby default),hasResource(type, definition)(whole resource, includingDeletionPolicy,DependsOn,Condition),resourceCountIs(type, n),resourcePropertiesCountIs(type, props, n),allResources()/allResourcesProperties(type, props)(every resource of the type),findResources(type, props)(returns matches). - Other sections:
hasOutput,hasParameter,hasMapping,hasCondition(and theirfind*versions);templateMatches(expected);toJSON()returns the template object (used for snapshots). - Matchers (
Match):objectLike(subset, the default behaviour),objectEquals(exact keys),arrayWith(subset, in order),arrayEquals,exact,anyValue(any non-null value),absent(key must not exist),not,serializedJson(match inside string-encoded JSON such as a Step Functions definition),stringLikeRegexp. Capturerecords a value inside a match for further checks:asString(),asNumber(),asObject().Annotations.fromStack(stack).hasError(path, message),hasWarning,hasNoError… test the messages your Aspects and validations add (§5).- Snapshot in Jest:
expect(template.toJSON()).toMatchSnapshot(); accept an intentional change by updating the stored snapshot (npx jest -u). - Run tests:
npm test(compile first for TypeScript, for exampletsc && npm test),python -m pytest,mvn test,dotnet test.
Developer angle
import { App } from 'aws-cdk-lib';
import { Capture, Match, Template } from 'aws-cdk-lib/assertions';
import { OrdersStack } from '../lib/orders-stack';
test('queue, table and consumer are wired correctly', () => {
const app = new App();
const template = Template.fromStack(new OrdersStack(app, 'TestStack'));
template.resourceCountIs('AWS::SQS::Queue', 2); // queue + dead-letter queue
template.hasResourceProperties('AWS::SQS::Queue', {
VisibilityTimeout: 300,
RedrivePolicy: Match.objectLike({ maxReceiveCount: 5 }),
});
template.hasResource('AWS::DynamoDB::Table', { DeletionPolicy: 'Retain' });
const role = new Capture();
template.hasResourceProperties('AWS::Lambda::Function', {
Role: { 'Fn::GetAtt': [role, 'Arn'] },
Environment: { Variables: { TABLE_NAME: Match.anyValue() } },
});
expect(role.asString()).toMatch(/^ConsumerServiceRole/);
});
test('template matches the approved snapshot', () => {
const app = new App();
const template = Template.fromStack(new OrdersStack(app, 'SnapshotStack'));
expect(template.toJSON()).toMatchSnapshot();
});
- Python (pytest) uses snake_case names:
from aws_cdk.assertions import Match, Template, thenTemplate.from_stack(stack).has_resource_properties("AWS::S3::Bucket", {...})withMatch.object_like(...).
Traps
- Trap: "Snapshot tests are the strongest regression guard." → Reality: fine-grained assertions are; snapshots break on harmless upstream changes and are best for refactoring.
- Trap: "Assertions tests deploy the stack to a test account." → Reality: they synthesize in memory and inspect the template.
- Trap: "
hasResourcePropertiesfails if the resource has properties you didn't list." → Reality: it is a partial match; useMatch.objectEqualsfor an exact match. - Trap: "Check
DeletionPolicywithhasResourceProperties." → Reality: it sits outsideProperties; usehasResource. - Trap: "
Template.fromStringaccepts a YAML template." → Reality: it expects a JSON string (or usefromJSONwith a parsed object). - Trap: "A snapshot that fails right after a CDK upgrade proves a bug." → Reality: review the difference; if it is expected, update the snapshot.
Practice questions
-
A developer must verify in a unit test that every Lambda function in a stack has
TracingConfigwithModeset toActive, and the test must keep passing when other function properties change. Which assertion fits BEST?- A.
expect(template.toJSON()).toMatchSnapshot() - B.
template.resourceCountIs('AWS::Lambda::Function', 3) - C.
template.hasResourceProperties('AWS::Lambda::Function', Match.objectEquals({ TracingConfig: { Mode: 'Active' } })) - D.
template.allResourcesProperties('AWS::Lambda::Function', { TracingConfig: { Mode: 'Active' } })
- A.
-
A team keeps a legacy CloudFormation JSON template next to a CDK app and wants Jest tests for both. Which TWO statements are correct? (Select TWO.)
- A.
Template.fromStack()synthesizes the CDK stack in memory, so the tests need no deployment. - B. Snapshot tests fail only when application logic changes, never after a CDK library upgrade.
- C.
hasResourceProperties()fails when the resource has any property not listed in the assertion. - D. Fine-grained assertions require the target environment to be bootstrapped first.
- E.
Template.fromString()can load the legacy JSON template for the same assertion methods.
- A.
Answers
- D.
allResourcesPropertieschecks every function for the listed property and ignores the rest. A snapshot fails on any unrelated change, a count says nothing about tracing, andobjectEqualsdemands that the function have no other properties. - A, E. Tests synthesize in memory, and
fromString(orfromJSON) wraps external templates. Snapshots also change with CDK upgrades,hasResourcePropertiesis a partial match, and nothing is deployed, so bootstrapping is irrelevant.
13. CDK vs SAM and local testing with the SAM CLI
Concept
- Both tools end in CloudFormation. AWS SAM is declarative: a YAML or JSON template with
Transform: AWS::Serverless-2016-10-31, serverless shorthand resource types and the SAM CLI for building, local testing and deploying. The CDK is imperative: code in six languages, constructs covering every AWS service, synthesized into CloudFormation. - They also work together: the SAM CLI can read the template the CDK synthesizes into
cdk.outand run the app's Lambda functions locally in Docker containers.
Exam facts
| AWS SAM | AWS CDK | |
|---|---|---|
| Authoring | YAML/JSON template with the SAM transform | TypeScript, JavaScript, Python, Java, C#/.NET or Go |
| Scope | Serverless-first (functions, APIs, tables, state machines) plus any CloudFormation resource | Every AWS service through L1/L2/L3 constructs |
| Output | Transformed CloudFormation template | Synthesized CloudFormation template + assets |
| Deploy | sam deploy |
cdk deploy (after cdk bootstrap) |
| Local Lambda testing | sam local invoke / start-api / start-lambda |
The same SAM CLI commands on the cdk.out template |
| Fast cloud iteration | sam sync --watch |
cdk watch / --hotswap |
| Unit tests of infrastructure | Template linting and validation | assertions module (§12) |
| Best when | Quick start on Lambda, YAML-first team, SAM-specific features | Many services, reusable typed components, logic in code |
- Local testing workflow: run
cdk synthfirst (after every change, since the SAM CLI readscdk.out);cdk synth --no-staginglets SAM use the original source files. Thensam local invoke <FunctionConstructId> -t ./cdk.out/<Stack>.template.json. - Choose the event with
--event events/file.json(or--no-event); create sample payloads withsam local generate-event(for exampleapigateway http-api-proxy); pass environment overrides with--env-vars. For a function inside a nested stack use<NestedStackId>/<FunctionId>. More onsam local→ seenotes/sam-guide.md#10-local-testing-with-sam-local-and-generated-events. sam local start-api -t ./cdk.out/<Stack>.template.jsonserves every API in the app locally;sam local start-lambda -t …starts a local Lambda endpoint for SDK or CLI invokes.- Docker is required (or an alternative that uses the Docker runtime): the SAM CLI runs each function in a local container image of the Lambda runtime. Local runs don't validate permissions between your real cloud resources, so still test in the cloud.
sam build -t ./cdk.out/<Stack>.template.jsonis needed only for container-image functions; zip-packaged functions just needcdk synth.sam builddoes not build runtime-specific constructs such asNodejsFunctionor bundled assets.- Moving a SAM app to the CDK (
cdk migrateis in preview and generates L1 constructs): deploy it and runcdk migrate --from-stack, or convert it to plain CloudFormation (for example withsam validate --debug) and runcdk migrate --from-path. - SAM-specific features (policy templates,
AutoPublishAliaswith CodeDeploy traffic shifting,sam sync) → seenotes/sam-guide.md.
Developer angle
cdk synth --no-staging
sam local invoke ResizeFunction -t ./cdk.out/ImageStack.template.json --event events/upload.json
sam local generate-event apigateway http-api-proxy > events/api.json
sam local start-api -t ./cdk.out/ImageStack.template.json # http://127.0.0.1:3000 by default
sam build -t ./cdk.out/ImageStack.template.json # only for container-image functions
The same function written both ways:
Transform: AWS::Serverless-2016-10-31
Resources:
ResizeFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: python3.12
Handler: app.handler
CodeUri: functions/resize/
new lambda.Function(this, 'ResizeFunction', {
runtime: lambda.Runtime.PYTHON_3_12,
handler: 'app.handler',
code: lambda.Code.fromAsset('functions/resize'),
});
Traps
- Trap: "The SAM CLI reads the CDK's TypeScript code directly." → Reality: it reads the synthesized template; run
cdk synthfirst and after each change. - Trap: "Run
sam buildbefore every local test of a CDK app." → Reality: only container-image functions need it; zip functions run from the synthesized template. - Trap: "
sam local invokeexecutes the function in the AWS account." → Reality: it runs in a local Docker container; it does not prove IAM permissions. - Trap: "
sam buildbundlesNodejsFunctionhandlers." → Reality: it skips runtime-specific constructs and bundled assets; the CDK bundles them during synthesis. - Trap: "Pick SAM for an app that is mostly ECS, RDS and networking." → Reality: that favours the CDK; SAM shines for serverless-first apps.
- Trap: "SAM and the CDK can't be combined." → Reality: the CDK defines and deploys; the SAM CLI tests and builds locally from
cdk.out.
Practice questions
-
A developer wants to run a Lambda handler defined in a CDK app on a laptop with a sample event before deploying anything. Docker and the SAM CLI are installed. Which sequence works?
- A. Run
sam initin the CDK project, thensam local invokewithout a template. - B. Run
cdk synth, thensam local invoke <FunctionId> -t ./cdk.out/<Stack>.template.json --event event.json. - C. Run
cdk deploy --hotswap, then call the function withaws lambda invoke. - D. Run
cdk watchwith--logsto stream the handler's output.
- A. Run
-
A team tests the Lambda functions of a CDK app locally with the SAM CLI. Which TWO statements are correct? (Select TWO.)
- A.
sam local invokeruns the function inside the team's AWS account. - B. The SAM CLI evaluates the CDK TypeScript constructs directly, so synthesis is not needed.
- C. The team must run
cdk synthbefore thesam localcommands and again after code changes. - D.
sam buildbundles the app'sNodejsFunctionhandlers with esbuild. - E.
sam buildis needed only for functions packaged as container images.
- A.
Answers
- B. The SAM CLI needs the synthesized template in
cdk.outand the function's construct ID; it then runs the handler in Docker with the given event. Without a template there is nothing CDK-defined to run, and the hotswap and watch options deploy to AWS instead of testing locally. - C, E. The SAM CLI reads
cdk.out, so synthesis must be current, and only image functions needsam build. Local invokes run in Docker on the laptop, the SAM CLI never executes construct code, andsam buildskipsNodejsFunction.
Quick decision rules
Fundamentals, constructs and stacks (§1–§3)
- If you see "define infrastructure for many AWS services in TypeScript, Python, Java, C# or Go", think AWS CDK (synthesizes CloudFormation).
- If you see "where does the template come from before deployment", think
cdk synth→ cloud assembly incdk.out. - If you see
Cloud assembly schema version mismatch, think upgrade the CDK CLI (npm install -g aws-cdk). - If you see a class name starting with
Cfn, think L1: one-to-one CloudFormation resource, no defaults. - If you see "least code for a standard multi-resource architecture" (API Gateway + Lambda, ALB + Fargate), think an L3 pattern (
LambdaRestApi,ApplicationLoadBalancedFargateService). - If you see "renamed or moved a construct and
cdk diffshows replacement", think logical ID changed: restore the ID, useDefault,overrideLogicalId()orcdk refactor. - If you see an export that "cannot be deleted as it is in use", think strong cross-stack reference: keep it one more deployment with
exportValue()or switch to weak references. - If you see "more than 500 resources in one stack", think
NestedStack(counts as one resource in its parent).
Permissions, customisation, assets and data (§4–§7)
- If you see "least privilege for a function to read a bucket or table", think the resource's grant (
bucket.grants.read(fn); olderbucket.grantRead(fn)). - If you see a KMS-encrypted bucket and a hand-written S3 policy that fails, think the grant also adds
kms:Decrypt. - If you see a pipeline
cdk deployhanging on a security prompt, think--require-approval never(defaultbroadening). - If you see "property exists in CloudFormation but not on the L2", think
node.defaultChild+ the typed L1 property (addPropertyOverride()if the L1 class lacks it too). - If you see "feature not available in CloudFormation at all", think custom resource (
AwsCustomResourcefor one SDK call). - If you see "fail synthesis for non-compliant resources across the app", think an Aspect with
Annotations.of(node).addError(). - If you see "tag everything in a stack or app", think
Tags.of(scope).add(). - If you see "Lambda code or Docker image packaged from a local directory", think assets published at
cdk deployto the bootstrap bucket / ECR repository. - If you see
NodejsFunctionfailing without Docker, think install esbuild locally. - If you see "bucket or table still there after
cdk destroy", think defaultRemovalPolicy.RETAIN. - If you see "delete a non-empty bucket with the stack", think
RemovalPolicy.DESTROY+autoDeleteObjects: true.
Bootstrapping, CLI and iteration (§8–§10)
- If you see
SSM parameter /cdk-bootstrap/hnb659fds/version not found, thinkcdk bootstrap aws://ACCOUNT/REGION. - If you see
Policy contains a statement with one or more invalid principalsin a CDK Pipeline, think the target account was not bootstrapped (with--trust). - If you see "new Region in an already bootstrapped account", think bootstrap again: one bootstrap per account and Region.
- If you see "pipeline account must deploy into another account", think
cdk bootstrap --trust <pipeline-account> --cloudformation-execution-policies <ARN>. - If you see "a pipeline that updates itself when new stages are added to the CDK app", think CDK Pipelines (
pipelines.CodePipeline+addStage()). - If you see "restrict what CDK deployments can create", think scoped
--cloudformation-execution-policies(defaultAdministratorAccess). - If you see "preview changes against what is deployed", think
cdk diff(--failfor CI gates). - If you see "keep partial progress after a failed dev deployment", think
cdk deploy --no-rollback, thencdk rollbackif needed. - If you see "Lambda code change live in seconds on a dev stack", think
cdk deploy --hotswaporcdk watch; never in production.
Environments, testing and SAM (§11–§13)
- If you see
Vpc.fromLookupfailing on a stack withoutenv, think set an explicit account and Region. - If you see "CI synthesizes a different template than laptops", think commit
cdk.context.json(and--no-lookupsin CI). - If you see "check that a resource has a property with a value" in a unit test, think
Template.fromStack()+hasResourceProperties(). - If you see "detect any unintended change to the whole template while refactoring", think a snapshot test.
- If you see "test a CDK-defined Lambda function locally", think
cdk synththensam local invoke -t ./cdk.out/<Stack>.template.json. - If you see "YAML-first serverless app, quick start", think SAM; "many services, reusable typed components", think CDK.
Changed vs older material
Learn the Current column; older practice material may still show the Course / older material value. Rows you confirm during /ingest are added to notes/current-facts.md.
| Topic | Course / older material | Current (verified 2026-10-12) | Source |
|---|---|---|---|
| §1 · Installing the CDK CLI | npm install -g aws-cdk-lib installs the CLI and libraries |
npm install -g aws-cdk installs the CLI; aws-cdk-lib + constructs are project dependencies added by cdk init |
AWS docs |
| §1 · Supported languages | JavaScript/TypeScript, Python, Java, .NET | TypeScript, JavaScript, Python, Java, C#/.NET and Go (all stable) | AWS docs |
| §1 · CLI and library versioning | CLI and aws-cdk-lib share one version number |
CLI (aws-cdk-cli) released separately on a 2.1xxx line; a library release works with the CLI current at its release and any newer CLI |
AWS docs |
| §2 · L1 constructs and L2 features | L1s can only be configured property by property | Mixins (.with(), Mixins.of()) add L2-style features to L1 and L2 constructs; Facades (Grants classes) add permissions helpers |
AWS docs |
| §2 · Lifecycle helper name | bucket.addLifeCycleRule() |
bucket.addLifecycleRule() |
AWS docs |
| §2 · ALB + Fargate L3 class name | aws-ecs-patterns.ApplicationLoadBalancerFargateService |
ecs_patterns.ApplicationLoadBalancedFargateService ("Balanced"; the "Balancer" name does not exist) |
AWS docs |
| §3 · Cross-stack references | Always an export + Fn::ImportValue; cross-Region needs crossRegionReferences: true (custom resources) |
Strong (default, export + Fn::ImportValue) or weak (Fn::GetStackOutput, no export lock, cross-account and cross-Region), set with @aws-cdk/core:defaultCrossStackReferences or CrossStackReferences.of() |
AWS docs |
| §3 · Renaming constructs | A changed logical ID always means replacement unless you restore the old ID | cdk refactor --unstable=refactor (preview) keeps resources when constructs are renamed or moved |
AWS docs |
| §4 · Granting access | bucket.grantRead(fn), table.grantReadWriteData(fn) |
bucket.grants.read(fn), table.grants.readWriteData(fn), BucketGrants.fromBucket(l1); old methods still work but are discouraged (fn.grantInvoke() unchanged) |
AWS docs |
§8 · Contents of CDKToolkit |
An S3 bucket and IAM roles | S3 bucket, ECR repository, 5 IAM roles, SSM parameter /cdk-bootstrap/hnb659fds/version; no KMS key by default |
AWS docs |
| §8 · Error when an environment isn't bootstrapped | Policy contains a statement with one or more invalid principal |
SSM parameter /cdk-bootstrap/hnb659fds/version not found. Has the environment been bootstrapped? Please run 'cdk bootstrap'; the invalid-principals message comes from CDK Pipelines deploying into an unbootstrapped account |
AWS docs |
§9 · cdk diff |
Compares template text only (--no-change-set to skip change sets) |
--method auto (default) creates a read-only change set for accurate replacement info; --method template for a text diff; --change-set deprecated |
AWS docs |
| §10 · Hotswap behaviour and coverage | --hotswap falls back to CloudFormation by itself; Lambda, Step Functions and ECS only |
Non-hotswappable changes are skipped unless --hotswap-fallback; coverage includes API Gateway, AppSync, CodeBuild, EventBridge, DynamoDB, SQS, CloudWatch, Bedrock and more |
AWS docs |
Re-checked and unchanged: AWS CDK v1 entered maintenance on 2022-06-01 and reached end of support on 2023-06-01; v2 ships stable constructs in aws-cdk-lib, Construct in constructs v10, experimental modules as -alpha packages, and requires the modern bootstrap template (AWS docs).