@go-to-k/cdkd 0.285.11 → 0.285.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,16 +6,23 @@
6
6
 
7
7
  Drop-in CDK CLI for existing CDK apps — up to 15x faster deploys via direct AWS SDK calls instead of CloudFormation.
8
8
 
9
+ **📚 Documentation: [cdkd.dev](https://cdkd.dev)**
10
+
11
+ <a href="https://cdkd.dev"><picture>
12
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/go-to-k/cdkd/main/assets/docs-site-dark.png">
13
+ <img alt="cdkd documentation site — cdkd.dev" src="https://raw.githubusercontent.com/go-to-k/cdkd/main/assets/docs-site-light.png" width="820">
14
+ </picture></a>
15
+
9
16
  - **Drop-in CDK compatible**: your existing CDK app code runs as-is; just replace `cdk deploy` with `cdkd deploy`.
10
17
  - **Up to 15x faster deploys**: direct SDK calls, aggressive parallelization, and `--no-wait` to skip slow stabilization waits; **faster than Terraform and CloudFormation Express mode** too (see [Benchmark](#benchmark)).
11
18
 
12
- ![cdk deploy vs cdkd deploy — side-by-side, 35s recording, real AWS deploy. cdkd finishes while cdk is still creating its CloudFormation changeset.](assets/cdk-vs-cdkd.gif)
19
+ ![cdk deploy vs cdkd deploy — side-by-side, 35s recording, real AWS deploy. cdkd finishes while cdk is still creating its CloudFormation changeset.](https://raw.githubusercontent.com/go-to-k/cdkd/main/assets/cdk-vs-cdkd.gif)
13
20
 
14
- **cdkd complements the AWS CDK CLI rather than replacing it.** Use cdkd in dev/test for rapid iteration; use the AWS CDK CLI in production for full CloudFormation tooling. Install cdkd alongside an existing `cdk deploy` workflow: no migration needed. You can also [import](#importing-existing-resources) existing stacks into cdkd or [export](#exporting-a-stack-back-to-cloudformation) back to CloudFormation anytime.
21
+ **cdkd complements the AWS CDK CLI rather than replacing it.** Use cdkd in dev/test for rapid iteration; use the AWS CDK CLI in production for full CloudFormation tooling. Install cdkd alongside an existing `cdk deploy` workflow: no migration needed. You can also [import](https://cdkd.dev/import/) existing stacks into cdkd or [export](https://cdkd.dev/export/) back to CloudFormation anytime.
15
22
 
16
23
  **A natural fit for AI-driven development.** AI coding agents iterate in tight spin-up / tear-down loops — and cdkd keeps each turn short, with fast deploys and an equally fast `cdkd destroy` that deletes via direct SDK calls instead of polling a CloudFormation stack-delete.
17
24
 
18
- **Local execution from your deployed stack.** `cdkd local` runs your functions, APIs, and ECS tasks on your machine. It can resolve env vars, secrets, and resource references from your real deployed stack: no hand-written `.env` files, no hand-seeded test data (see [Local execution](#local-execution)).
25
+ **Local execution from your deployed stack.** `cdkd local` runs your functions, APIs, and ECS tasks on your machine. It can resolve env vars, secrets, and resource references from your real deployed stack: no hand-written `.env` files, no hand-seeded test data (see [Local execution](https://cdkd.dev/local-emulation/)).
19
26
 
20
27
  > [!IMPORTANT]
21
28
  > cdkd is for dev/test workflows only — early in development, not yet production-ready.
@@ -33,7 +40,7 @@ The installed binary is `cdkd`.
33
40
 
34
41
  > **First-time setup**: run `cdkd bootstrap` once per AWS account before any
35
42
  > other command; it replaces `cdk bootstrap`, which cdkd does not require
36
- > (details in [Prerequisites](#prerequisites)).
43
+ > (details in [Getting Started](https://cdkd.dev/getting-started/)).
37
44
 
38
45
  ```bash
39
46
  # Bootstrap (creates S3 state bucket + asset storage — one-time setup per AWS account)
@@ -57,15 +64,12 @@ cdkd destroy
57
64
 
58
65
  ## Use with AI Coding Agents
59
66
 
60
- This repository ships a [`cdkd` skill](plugins/cdkd-skills/skills/cdkd/SKILL.md)
61
- that teaches AI coding agents to use cdkd safely: install, AWS preflight
62
- checks, preview and deployment, wait modes, verification, CloudFormation
63
- migration boundaries, and destructive-operation safety.
67
+ This repository ships a [`cdkd` skill](https://github.com/go-to-k/cdkd/blob/main/plugins/cdkd-skills/skills/cdkd/SKILL.md)
68
+ that teaches AI coding agents to use cdkd safely ([AI agents guide](https://cdkd.dev/ai-agents/)).
64
69
 
65
70
  ### Claude Code
66
71
 
67
- Install it as a plugin (one-time setup, available in every session) by
68
- running these inside a Claude Code session:
72
+ Install it as a plugin (one-time setup) by running these inside a Claude Code session:
69
73
 
70
74
  ```text
71
75
  /plugin marketplace add go-to-k/cdkd
@@ -98,12 +102,20 @@ linking the checkout:
98
102
  claude --add-dir /path/to/cdkd
99
103
  ```
100
104
 
105
+ ## Documentation
106
+
107
+ Full documentation lives at **[cdkd.dev](https://cdkd.dev)**:
108
+
109
+ - [Getting Started](https://cdkd.dev/getting-started/)
110
+ - [CLI Reference](https://cdkd.dev/cli-reference/)
111
+ - [Local Execution](https://cdkd.dev/local-emulation/)
112
+ - [Import / Export — CloudFormation migration](https://cdkd.dev/import/)
113
+ - [Troubleshooting](https://cdkd.dev/troubleshooting/)
114
+
101
115
  ## Benchmark
102
116
 
103
117
  **cdkd deploys up to 15x faster than AWS CDK (CloudFormation)** on SDK-Provider-handled stacks; the per-stack speedup widens with size and parallelism.
104
118
 
105
- Numbers below are deploy-phase only (CDK app synthesis is identical between cdkd and AWS CDK — both run the same user code through `aws-cdk-lib`'s synthesizer — so synth time is excluded from the speedup calculation).
106
-
107
119
  ### vs CloudFormation Express mode: up to 9x faster
108
120
 
109
121
  CloudFormation's [Express mode](https://aws.amazon.com/about-aws/whats-new/2026/06/aws-cloudformation-cdk/) is a fast-deploy option that skips resource stabilization waits, similar in spirit to cdkd's `--no-wait`. Even so, cdkd is faster than Express on nearly every stack, and with `--no-wait` it pulls dramatically ahead on stacks dominated by async resources.
@@ -119,11 +131,7 @@ CloudFormation's [Express mode](https://aws.amazon.com/about-aws/whats-new/2026/
119
131
  | SQS | 83 | 22 | **9** | 9 |
120
132
  | SQS + CloudWatch | 87 | 44 | 30 | 31 |
121
133
 
122
- Best of 3 runs, deploy-phase only, seconds, `us-west-2`. The `VPC + Lambda + SQS + CloudFront` stack is 1 VPC (2 AZs, NAT Gateway, public + private subnets) + VPC Lambda + Lambda Function URL + CloudFront Distribution + SQS + EventSourceMapping + Consumer Lambda. Its cdkd default cell was re-measured 2026-07-31 on cdkd 0.272.0 (96 / 107 / 115s, best of 3): since #1282 the default no longer waits for CloudFront `Deployed`, so NAT stabilization is the critical path. The other cells are from the original campaign.
123
-
124
- - **~1.5–2x faster than Express on most stacks** — e.g. SQS finishes in 9s vs Express's 22s (~2.4x).
125
- - **Async-heavy stacks are where the gap explodes.** On the VPC + CloudFront stack the cdkd default finishes in 96s vs Express's 366s (~3.8x) — since #1282 the default already leaves CloudFront propagation to complete in the background — and `--no-wait` (40s, ~9x) additionally skips the NAT stabilization wait.
126
- - **S3 is the one case where Express edges cdkd's default** (22s vs 23s). On a near-instant single-resource stack there is little left to parallelize, and `--no-wait` makes no difference there.
134
+ Best of 3 runs, deploy-phase only, seconds, `us-west-2`.
127
135
 
128
136
  ### vs Terraform: cdkd deploys faster
129
137
 
@@ -139,948 +147,9 @@ We also raced cdkd against Terraform: the same logical stacks expressed both as
139
147
  | cloudfront — created (fire and forget) | S3 origin + CloudFront + OAC | 11.1 | 10.3 (`wait_for_deployment = false`) | no such mode | 10.4 |
140
148
  | cloudfront — `Deployed` | S3 origin + CloudFront + OAC | 174.0 (`--full-wait`) | 166.4 | 232.3 | n/a |
141
149
 
142
- Cold end-to-end wall clock including synth / plan, median of 7 runs, seconds, `us-east-1`; every run gets fresh resource names, so every run measures a first deploy. The cloudfront scenario is two rows because since #1282 the tools' DEFAULTS differ in what "done" means there — each row compares tools held to the same completion definition (cdkd's default is the fire-and-forget row; Terraform's default is the `Deployed` row), re-measured 2026-07-31 on cdkd 0.272.0.
143
-
144
- - **The lead tracks how much of the wall clock is orchestration.** wide and serverless are almost pure orchestration and run ~2.2x faster; cloudfront is almost pure CDN propagation under the `Deployed` definition and pure API accept under fire-and-forget — a tie in both rows.
145
- - **A win is claimed only where no cdkd run overlaps any Terraform run** — true of the four bolded rows. The ties disclose which way their medians lean, and the rule cuts both ways: webapp leans cdkd (by 17.6s), both cloudfront rows lean Terraform (by 0.8s and 7.6s) — in all three the run distributions overlap fully, so n=7 cannot separate them and neither side gets the row.
146
- - **This is not cdkd waiting for less.** Held to the same completion definition — ECS `--full-wait` vs Terraform's `wait_for_steady_state=true` is 227.7 vs 282.7 (1.24x), and both cloudfront rows above are same-definition pairs by construction.
147
-
148
- Distribution analysis, wait-skipping comparability (Terraform has no global `--no-wait` equivalent), and per-run data: [docs/benchmarks.md](docs/benchmarks.md) and [cdkd-bench-terraform](https://github.com/go-to-k/cdkd-bench-terraform).
149
-
150
- ### More benchmarks
151
-
152
- The full benchmark suite lives in [docs/benchmarks.md](docs/benchmarks.md): the SDK Provider path (**5.5x**, 17.0s vs 94.4s), the VPC + CloudFront + Lambda stack behind the headline **15x** (40s vs 599s with `--no-wait`), the Cloud Control API fallback path (**1.6x**), and the Terraform comparison in full detail. Reproduction scripts: [tests/benchmark](tests/benchmark/README.md).
153
-
154
- ## Features
155
-
156
- - **Synthesis orchestration**: CDK app subprocess execution, Cloud Assembly parsing, context provider loop
157
- - **Asset handling**: Self-implemented asset publisher for S3 file assets (ZIP packaging) and Docker images (ECR)
158
- - **Context resolution**: Self-implemented context provider loop for Vpc.fromLookup(), AZ, SSM, HostedZone, etc.
159
- - **Hybrid provisioning**: SDK Providers for fast direct API calls, Cloud Control API fallback for broad resource coverage
160
- - **Diff calculation**: Self-implemented resource/property-level diff between desired template and current state
161
- - **S3-based state management**: No DynamoDB required, uses S3 conditional writes for locking
162
- - **DAG-based parallelization**: Analyze `Ref`/`Fn::GetAtt` dependencies and execute in parallel
163
- - **Rollback on failure**: When a deploy errors mid-stack, cdkd rolls back the resources it just created so the stack state stays consistent (CloudFormation parity — but cdkd does this without round-tripping through CFn). Pass `cdkd deploy --no-rollback` to skip rollback and keep the partial state for Terraform-style inspection / repair — then either fix forward with another `cdkd deploy`, revert with the standalone `cdkd rollback`, or `cdkd destroy` to clean up. See [Rollback behavior](#rollback-behavior).
164
- - **`--no-wait` / `--full-wait` for async resources**: `--no-wait` skips the multi-minute wait on RDS / ElastiCache / NAT Gateway / EC2 Instance / ELBv2 LoadBalancer / Lambda MicroVM Image and returns as soon as the create call returns (CloudFormation always blocks); `--full-wait` goes the other way and waits where cdkd's default does not (ECS Service steady state; CloudFront Distribution `Deployed` — the default returns as soon as `CreateDistribution` is accepted, since nothing in-deploy needs the 3–15 min edge propagation)
165
- - **VPC route DependsOn relaxation (on by default)**: Drop CDK-injected defensive `DependsOn` edges from VPC Lambdas onto private-subnet routes so `CloudFront::Distribution` and `Lambda::Url` start their ~3-min propagation in parallel with NAT Gateway stabilization (~50% faster on VPC + Lambda + CloudFront stacks). Pass `--no-aggressive-vpc-parallel` to opt out.
166
- - **Local execution** (`cdkd local invoke` / `start-api` / `run-task` / `start-service` / `start-alb` / `start-cloudfront` / `invoke-agentcore` / `start-agentcore`): run Lambdas, API Gateway routes, ECS tasks, long-running ECS services, CloudFront distributions, and Bedrock AgentCore Runtimes from your CDK code. All AWS Lambda runtimes, container Lambdas, REST v1 / HTTP v2 / Function URL routes, Service Connect / Cloud Map, AgentCore HTTP / MCP / A2A / AGUI / WebSocket protocols (one-shot `invoke-agentcore` and long-running warm serve via `start-agentcore`, which serves the native contract — `POST /invocations` + `GET /ping`, MCP `/mcp`, A2A `/` — plus the `/ws` bridge for HTTP / AGUI). The Docker-backed commands work for both `cdkd deploy`-managed (`--from-state`) AND `cdk deploy`-managed (`--from-cfn-stack`) stacks; `start-cloudfront` serves the viewer-request -> S3 / Lambda Function URL origin -> viewer-response pipeline (CloudFront-Functions + S3-only distributions run in-process with no Docker). See [Local execution](#local-execution).
167
- - **Bidirectional CloudFormation migration**: `cdkd import --migrate-from-cloudformation` adopts existing CFn stacks (including `cdk deploy`-managed) into cdkd state without re-creating resources; `cdkd export` hands a cdkd stack back to CloudFormation when production-ready. See [Importing](#importing-existing-resources) / [Exporting](#exporting-a-stack-back-to-cloudformation).
168
- - **Mixed cdkd / CloudFormation estates**: a cdkd-deployed stack can reference a producer stack still managed by `cdk deploy` — `Fn::ImportValue` / `Fn::GetStackOutput` fall back to CloudFormation (`ListExports` / stack outputs) when the reference is not in cdkd state, so shared infrastructure stays on the CDK CLI while app stacks iterate on cdkd. See [Reference CloudFormation-managed stacks](#reference-cloudformation-managed-stacks-mixed-estates).
169
-
170
- > **Note**: Resource types not covered by either SDK Providers or Cloud Control API cannot be deployed with cdkd. Deployment fails with a clear error message naming the type + a 1-click issue link.
171
-
172
- ## How it works
173
-
174
- ```
175
- ┌─────────────────┐
176
- │ Your CDK App │ (aws-cdk-lib)
177
- └────────┬────────┘
178
-
179
-
180
- ┌─────────────────┐
181
- │ cdkd Synthesis │ Subprocess + Cloud Assembly parser
182
- └────────┬────────┘
183
-
184
-
185
- ┌─────────────────┐
186
- │ CloudFormation │
187
- │ Template │
188
- └────────┬────────┘
189
-
190
-
191
- ┌─────────────────┐
192
- │ Asset Build & │ S3 ZIP upload / ECR image build & push
193
- │ Publish │
194
- └────────┬────────┘
195
-
196
-
197
- ┌─────────────────┐
198
- │ cdkd Engine │
199
- │ - DAG Analysis │ Dependency graph construction
200
- │ - Diff Calc │ Compare with existing resources
201
- │ - Parallel Exec │ Dispatch on deps complete (no level barrier)
202
- └────────┬────────┘
203
-
204
- ┌────┴────┐
205
- ▼ ▼
206
- ┌────────┐ ┌────────┐
207
- │ SDK │ │ Cloud │
208
- │Provider│ │Control │ Fallback for many
209
- │ │ │ API │ additional types
210
- └────────┘ └────────┘
211
- ```
212
-
213
- For a step-by-step walkthrough of the full `cdkd deploy` pipeline (CLI
214
- parsing → synthesis → asset publishing → per-stack deploy), see
215
- [docs/architecture.md](docs/architecture.md#5-end-to-end-pipeline-walkthrough-cdkd-deploy).
216
-
217
- ## Prerequisites
218
-
219
- - **Node.js** >= 20.0.0
220
- - **AWS credentials with admin-equivalent permissions** for the resources being deployed. cdkd does NOT route through CloudFormation, so CDK CLI's `cdk-hnb659fds-deploy-role-*` is NOT sufficient — see [`--role-arn`](docs/cli-reference.md).
221
-
222
- AWS CDK's `cdk bootstrap` is not required. Instead, run `cdkd bootstrap` once per
223
- account: it creates the S3 state bucket (`cdkd-state-{accountId}`) that cdkd uses
224
- to track deployed resources, plus cdkd-owned asset storage (by default a
225
- `cdkd-assets-{accountId}-{region}` bucket + a
226
- `cdkd-container-assets-{accountId}-{region}` ECR repo; custom names via
227
- `--asset-bucket` / `--container-repo`, skip with `--no-assets`; see
228
- [`cdkd bootstrap`](docs/cli-reference.md#cdkd-bootstrap)). Per-region asset
229
- storage is added automatically on the first `cdkd deploy` into each region.
230
- Existing setups, legacy-mode opt-outs, and how this relates to `cdk bootstrap`: see
231
- [Upgrading from an earlier cdkd version](#upgrading-from-an-earlier-cdkd-version).
232
-
233
- ### Upgrading from an earlier cdkd version
234
-
235
- **No breaking change, no manual step: just deploy.** The first `cdkd deploy` into
236
- each region auto-creates the cdkd-owned asset storage (interactive runs are asked
237
- once per region, `--yes` / CI runs create it automatically) and shows a one-time
238
- in-place UPDATE repointing asset references — content identical, no replacement.
239
- Downgrading is safe too (older binaries ignore the marker). If you bootstrapped
240
- under a previous cdkd version, the legacy region-suffixed state bucket name
241
- (`cdkd-state-{accountId}-{region}`) is still picked up automatically with a
242
- deprecation warning. Explicit pre-provisioning
243
- (`cdkd bootstrap --region <r>`), legacy-mode opt-outs, and how this relates to
244
- `cdk bootstrap`: see [`cdkd bootstrap`](docs/cli-reference.md#cdkd-bootstrap).
245
-
246
- ## Usage
247
-
248
- cdkd has three command families:
249
-
250
- - **Top-level commands** (`cdkd deploy` / `destroy` / `diff` / `synth` /
251
- `list` / `import` / `orphan` / `publish-assets`) require a CDK app —
252
- they synthesize a template to learn what they're operating on.
253
- - **`cdkd state ...` subcommands** (`state info` / `list` / `resources`
254
- / `show` / `orphan` / `destroy` / `migrate` / `refresh-observed`)
255
- operate on the S3 state bucket only and do NOT need the CDK app —
256
- use them to inspect / clean up state when the source is gone or
257
- you don't want to synth. `cdkd state destroy` is the CDK-app-free
258
- counterpart of `cdkd destroy`.
259
- - **`cdkd local ...` subcommands** (`local invoke` / `start-api` /
260
- `run-task` / `start-service`) run synthesized workloads locally
261
- inside Docker containers — no AWS deploy needed. Modeled on
262
- `sam local *` but reads CDK state directly via `--from-state`
263
- (cdkd-managed) or `--from-cfn-stack` (CFn-managed). See
264
- [Local execution](#local-execution).
265
-
266
- Options like `--app`, `--state-bucket`, and `--context` can be omitted if configured via `cdk.json` or environment variables (`CDKD_APP`, `CDKD_STATE_BUCKET`).
267
-
268
- ```bash
269
- # Synth + deploy
270
- cdkd synth
271
- cdkd deploy # single-stack auto-detected
272
- cdkd deploy MyStack # by name (or 'MyStage/Api' display path)
273
- cdkd deploy --all
274
- cdkd deploy --dry-run # plan only, no changes
275
- cdkd deploy --no-rollback # Terraform-style: keep partial state on failure
276
- cdkd rollback MyStack # revert a failed --no-rollback / interrupted deploy
277
- cdkd deploy --no-wait # skip multi-minute waits (RDS / ElastiCache / NAT)
278
- cdkd deploy --full-wait # also wait where cdkd's default does not (ECS steady state, CloudFront Deployed)
279
-
280
- # Inspect what would change
281
- cdkd diff MyStack
282
- cdkd diff MyStack --fail # exit 1 on any change (CI gate)
283
-
284
- # Drift detection — compare state vs AWS reality (no synth)
285
- cdkd drift MyStack # exit 1 if drift
286
- cdkd drift MyStack --accept --yes # state ← AWS
287
- cdkd drift MyStack --revert --yes # AWS ← state
288
-
289
- # State secret hygiene — clean + audit. Keeps cdkd state free of sensitive
290
- # plaintext: a resolved secret dynamic reference is stored as its
291
- # {{resolve:...}} expression. No deploy, no AWS mutation.
292
- cdkd scrub MyStack # clean existing state in place
293
- cdkd scrub MyStack --dry-run # audit only, report what would change
294
- cdkd scrub MyStack --dry-run --fail # standing CI gate: exit 1 if plaintext remains
295
-
296
- # Asset / destroy / unlock
297
- cdkd publish-assets # synth + upload only (typical CI split)
298
- cdkd destroy MyStack
299
- cdkd orphan MyStack/MyBucket # drop one resource from state (AWS resource stays)
300
- cdkd force-unlock MyStack # clear stale lock from an interrupted deploy / cancelled CI job
301
- cdkd gc --dry-run # reclaim unreferenced cdkd-owned assets (S3 + ECR)
302
-
303
- # Migrate between cdkd and CloudFormation
304
- cdkd import MyStack --yes # adopt existing AWS resources into cdkd state
305
- cdkd export MyStack # hand a cdkd-managed stack back to CloudFormation
306
-
307
- # State-bucket-only commands (no CDK app needed)
308
- cdkd state info # bucket name, region, schema version
309
- cdkd state list # one row per (stackName, region)
310
- cdkd state list --tree # parent → child nested-stack tree
311
- cdkd state show MyStack # full state record
312
- cdkd state resources MyStack # logical id / type / physical id
313
- cdkd state destroy MyStack # delete AWS resources + state, no CDK app
314
- cdkd state orphan MyStack # remove state record only (AWS resources stay)
315
- ```
316
-
317
- See **[docs/cli-reference.md](docs/cli-reference.md)** for the full flag
318
- matrix (`--concurrency`, `--no-aggressive-vpc-parallel`,
319
- `--allow-unsupported-properties`, `--role-arn`, etc.), per-command details
320
- including the synth-driven per-resource `cdkd orphan <constructPath>`
321
- variant, and stage / wildcard pattern matching.
322
-
323
- ## `--no-wait` / `--full-wait`: choose what "done" means
324
-
325
- CloudFront / RDS / ElastiCache / NAT Gateway / EC2 Instance / ELBv2
326
- LoadBalancer typically take 1–15 minutes to fully provision. Three
327
- modes, least to most waiting:
328
-
329
- - **`--no-wait`** returns as soon as the create call returns and lets
330
- AWS finish in the background.
331
- - **default** waits where the wait is load-bearing (something the same
332
- deploy resolves or verifies needs the settled state, or the wait can
333
- surface a failure) — in practice, where CloudFormation and Terraform
334
- agree, with one measured exception: CloudFront Distribution returns
335
- as soon as `CreateDistribution` is accepted (nothing in-deploy needs
336
- the 3–15 min edge propagation, and the wait cannot detect a failure).
337
- - **`--full-wait`** additionally waits everywhere CloudFormation does
338
- (today: ECS Service steady state, CloudFront Distribution
339
- `Deployed`).
340
-
341
- Pick by whether anything downstream needs the resource to actually be
342
- serving, not by where the deploy runs — cutting billed CI minutes is a
343
- perfectly good reason to use `--no-wait` in CI, and a local smoke test
344
- (or a pipeline that wants CloudFormation-parity completion as a standing
345
- setting) is a good reason to use `--full-wait`. The two flags are
346
- opposite ends of one axis and cannot be combined.
347
-
348
- **Deploy-only**: `cdkd destroy` always waits (NAT in `deleting` state
349
- holds ENIs and would `DependencyViolation` sibling deletes).
350
-
351
- See [docs/cli-reference.md](docs/cli-reference.md#wait-semantics) for
352
- the per-resource table (what each mode does, next to what
353
- CloudFormation and Terraform do) and caveats (NAT egress, RDS
354
- final-snapshot timing, etc.).
355
-
356
- ## Use in CI: per-PR environments
357
-
358
- Deploy time is CI job time, and a PR environment redeploys on every
359
- push — so cdkd's speedup compounds across a PR's lifetime. Because
360
- cdkd needs **zero CDK code changes**, you can swap only the PR-environment
361
- workflow to cdkd and keep production / staging on the CDK CLI; switching
362
- back is a one-line workflow revert.
363
-
364
- Run `cdkd bootstrap` once per AWS account beforehand (creates the state
365
- bucket + asset storage; `cdk bootstrap` is not required).
366
-
367
- **One stack per PR** — pass the PR number as CDK context and suffix the
368
- stack name; cdkd state is keyed by (stack name, region) and locks are
369
- per-stack, so PR environments deploy concurrently without contention:
370
-
371
- ```ts
372
- const prNumber = app.node.tryGetContext('prNumber');
373
- new WebAppStack(app, `WebApp${prNumber ? `-pr-${prNumber}` : ''}`);
374
- ```
375
-
376
- **Credentials** — cdkd calls AWS APIs directly, so the deploying
377
- identity needs permissions for every deployed resource (CDK's
378
- `cdk-hnb659fds-*` roles do not work: they are designed for
379
- CloudFormation delegation, and cdkd uses its own bootstrap storage).
380
- Create a dedicated deploy role and switch into it with
381
- [`--role-arn`](docs/cli-reference.md#--role-arn) (or the
382
- `CDKD_ROLE_ARN` env var): the workflow's OIDC base role needs only
383
- `sts:AssumeRole` on the deploy role, and the deploy role's trust policy
384
- allows only that base role — the strong permissions live in exactly one
385
- place, reachable through one path, and never sit on the CI runner
386
- itself.
387
-
388
- **Minimal GitHub Actions shape** (deploy on open/sync/reopen, destroy
389
- on close):
390
-
391
- ```yaml
392
- on:
393
- pull_request:
394
- types: [opened, synchronize, reopened, closed]
395
- permissions: { id-token: write, contents: read }
396
- env:
397
- CDKD_ROLE_ARN: arn:aws:iam::123456789012:role/cdkd-deploy-role
398
- jobs:
399
- deploy:
400
- if: github.event.action != 'closed'
401
- runs-on: ubuntu-latest
402
- steps:
403
- - uses: actions/checkout@v7
404
- - uses: actions/setup-node@v7
405
- with: { node-version: 24, cache: npm }
406
- - run: npm ci
407
- - uses: aws-actions/configure-aws-credentials@v6
408
- with:
409
- role-to-assume: arn:aws:iam::123456789012:role/github-actions-base
410
- aws-region: us-east-1
411
- - run: npx cdkd deploy --yes -c prNumber=${{ github.event.pull_request.number }}
412
- destroy:
413
- if: github.event.action == 'closed'
414
- runs-on: ubuntu-latest
415
- steps:
416
- - uses: aws-actions/configure-aws-credentials@v6
417
- with:
418
- role-to-assume: arn:aws:iam::123456789012:role/github-actions-base
419
- aws-region: us-east-1
420
- - run: npx @go-to-k/cdkd state destroy WebApp-pr-${{ github.event.pull_request.number }} --yes
421
- ```
422
-
423
- The destroy job has no checkout, `npm ci`, or synth —
424
- [`cdkd state destroy`](#orphan-vs-destroy) deletes from the state
425
- record alone, so it works even after the branch is gone.
426
-
427
- If the environment contains protection-enabled resources (RDS /
428
- DynamoDB deletion protection, EC2 termination protection, and more),
429
- add [`--remove-protection`](#--remove-protection-one-shot-bypass-for-protected-resources)
430
- to the destroy so the teardown completes in one pass — an ephemeral PR
431
- environment has nothing worth protecting, and without the flag those
432
- resources survive the job and linger until the next sweep.
433
-
434
- Two more teardown-completeness flags matter for disposable
435
- environments: resources with `DeletionPolicy: Snapshot` leave a final
436
- snapshot behind on every PR close by default — add
437
- [`--skip-final-snapshot`](#deletionpolicy-snapshot-final-snapshots-on-delete)
438
- when the environment's data is disposable, so snapshots don't
439
- accumulate per closed PR. And `cdkd destroy --purge-events` also
440
- removes the stack's deployment-event history so the state bucket
441
- returns fully empty (after a `state destroy`, the equivalent is
442
- `cdkd events prune <stack> --all`).
443
-
444
- **Housekeeping**:
445
-
446
- - Pick the wait mode from what runs next (see the section above):
447
- review-only environments can use `--no-wait`; E2E tests after the
448
- deploy should keep the default, or `--full-wait` when they need ECS
449
- steady state / CloudFront propagation.
450
- - A job cancelled mid-deploy (e.g. `concurrency.cancel-in-progress`)
451
- can leave a stack lock; it expires on its own TTL (30 minutes), or
452
- run `cdkd force-unlock <stack>` to clear it immediately.
453
- - Sweep forgotten environments with `cdkd state list --json` on a
454
- schedule, and reclaim unreferenced assets with
455
- `cdkd gc --older-than 30d --dry-run` — `gc` aborts if any stack is
456
- locked, so schedule it outside deploy hours.
457
- - To comment the environment URL on the PR, read stack outputs with
458
- `cdkd state show <stack> --json`.
459
- - To gate a PR without deploying, `cdkd diff --fail` exits `1` when any
460
- change is detected (and `cdkd drift --json` machine-checks live
461
- divergence). See [Exit codes](#exit-codes) for per-command semantics.
462
-
463
- ## Rollback behavior
464
-
465
- When a deploy fails mid-stack (e.g. a resource hits a validation error
466
- or AWS rejects the request), cdkd by default **rolls back the
467
- already-completed resources in the same deploy** so the stack state
468
- stays consistent — every resource cdkd just created in this run is
469
- deleted in reverse dependency order, the state record is updated to
470
- match, and the CLI exits non-zero. Resources that existed before this
471
- deploy are NOT touched.
472
-
473
- Pass `cdkd deploy --no-rollback` to skip the rollback (Terraform-style:
474
- the partial state is preserved so you can `cdkd state show <stack>`,
475
- inspect what landed, fix the underlying issue, and re-run `cdkd deploy`
476
- to continue from the half-deployed state). Recommended only when you
477
- plan to manually inspect / repair; the default is safer for CI.
478
-
479
- Mid-deploy state is also saved per-resource as work completes, so even
480
- if cdkd itself crashes between the failure and the rollback, the state
481
- file accurately reflects what's on AWS and a follow-up `cdkd destroy`
482
- won't orphan anything.
483
-
484
- ### `cdkd rollback` — revert a failed deploy
485
-
486
- After a `--no-rollback` failure (or a Ctrl+C-interrupted deploy, or an
487
- automatic rollback that itself died partway), you have three options:
488
- fix forward (`cdkd deploy` again), revert (`cdkd rollback`), or clean up
489
- (`cdkd destroy`). The standalone `cdkd rollback` command is the "revert"
490
- option — the cdkd equivalent of `cdk rollback` / CloudFormation
491
- `RollbackStack`:
492
-
493
- ```bash
494
- cdkd rollback MyStack # revert MyStack to its pre-deploy state
495
- cdkd rollback # single journaled stack (no arg)
496
- cdkd rollback MyStack --force # skip the confirmation prompt
497
- ```
498
-
499
- It works from a **rollback journal** cdkd writes to
500
- `s3://bucket/cdkd/{stack}/{region}/rollback-journal.json` (a sibling of
501
- `state.json`) whenever a deploy ends without a completed rollback — the
502
- journal records the exact operations that completed, so `cdkd rollback`
503
- replays them in reverse (deleting created resources, restoring updated
504
- ones) with no synth and no CDK app needed. It is **synth-free** on
505
- purpose: a broken app is a common reason you want to roll back. The
506
- journal is deleted automatically on the next successful deploy and by
507
- `cdkd destroy`; after a clean automatic rollback it keeps only the
508
- failed resource's record so `cdkd rollback --revert-failed` still works
509
- in the default deploy flow.
510
-
511
- Flags: `--force` (skip confirm), `--orphan <logicalId>` (repeatable —
512
- leave the resource alone during replay, like `cdk rollback --orphan`),
513
- `--revert-failed` (also attempt to revert the resource whose operation
514
- FAILED mid-deploy — off by default because its remote state is unknown; its
515
- delete honors `DeletionPolicy` the same way a completed CREATE's does),
516
- `--stack-region <region>` (disambiguate a same-named stack across
517
- regions), `--role-arn`, `--state-bucket`. A **replacement** is reverted
518
- by reversing it: the old resource is re-created from its journaled
519
- pre-deploy state and the new one deleted (for a stateful type the old
520
- data is unrecoverable — warned loudly). Exit codes: `0` = fully clean
521
- (journal deleted), `2` = partial (some ops failed / were skipped — the
522
- journal is kept so you can re-run), `1` = hard error. See
523
- [docs/cli-reference.md](docs/cli-reference.md#cdkd-rollback) for the
524
- full reference and known limitations (a DELETE that already happened
525
- cannot be restored).
526
-
527
- ## Importing existing resources
528
-
529
- `cdkd import` adopts AWS resources that are already deployed (via
530
- `cdk deploy`, manual creation, or another tool) into cdkd state so the
531
- next `cdkd deploy` updates them in-place instead of CREATEing duplicates.
532
-
533
- `cdkd import --migrate-from-cloudformation` extends this to migrate a
534
- **whole CloudFormation stack** off CFn in a single command: cdkd reads
535
- the source CFn stack's `(logicalId, physicalId)` mappings, adopts every
536
- resource into cdkd state, then retires the source CFn stack (injects
537
- `DeletionPolicy: Retain` + `UpdateReplacePolicy: Retain` on every
538
- resource → `UpdateStack` → `DeleteStack`) so the AWS resources stay
539
- intact but are no longer tracked by CFn. After the command finishes,
540
- the stack is managed by `cdkd deploy`. This is the reverse direction
541
- of `cdkd export` (see below).
542
-
543
- ```bash
544
- # Adopt a whole stack: each resource is resolved from its template name property,
545
- # then from a same-named CloudFormation stack (#1128). Use
546
- # --migrate-from-cloudformation below when you also want that stack retired.
547
- cdkd import MyStack --yes
548
-
549
- # Adopt only specific resources (CDK CLI parity).
550
- cdkd import MyStack --resource MyBucket=my-bucket-name
551
-
552
- # Some types use a composite, `|`-delimited physical id — quote it.
553
- # Full per-type table: docs/state-management.md#composite-pipe-delimited-physicalids
554
- cdkd import MyStack --resource 'MyGlueTable=my_database|my_table'
555
-
556
- # Migrate off CloudFormation in one shot — adopt + retire the source CFn stack.
557
- cdkd import MyStack --migrate-from-cloudformation --yes
558
- ```
559
-
560
- See **[docs/import.md](docs/import.md)** for the full guide: three import
561
- modes (auto / selective / hybrid), `--resource-mapping` CDK CLI
562
- compatibility, CloudFormation migration flow, provider coverage, and the
563
- parity matrix vs upstream `cdk import`.
564
-
565
- ## Exporting a stack back to CloudFormation
566
-
567
- `cdkd export` is the mirror of `cdkd import`: it hands a cdkd-managed
568
- stack back to CloudFormation via a CFn `ChangeSetType=IMPORT` changeset.
569
- AWS resources are unchanged across the migration; cdkd state for the
570
- exported stack is deleted on success. From then on the stack is managed
571
- by `cdk deploy` / `aws cloudformation`. Accepts JSON and YAML templates
572
- (shorthand intrinsics round-trip).
573
-
574
- ```bash
575
- cdkd export MyStack # confirmation prompt; CFn stack name = cdkd stack name
576
- cdkd export MyStack --cfn-stack-name MyStack-CFn
577
- cdkd export MyStack --dry-run # print the import plan, do not call CFn
578
- cdkd export MyStack --include-non-importable # 2-phase: IMPORT importable + CFn-CREATE Custom Resources
579
- cdkd export MyApp # nested-stack tree: leaf-first per-stack IMPORT loop
580
- ```
581
-
582
- **Lambda-backed Custom Resources** (`Custom::*` /
583
- `AWS::CloudFormation::CustomResource`) are NOT directly CFn-importable.
584
- `--include-non-importable` opts into a 2-phase migration that re-CREATEs
585
- them through CFn — the Custom Resource Lambda must be idempotent.
586
- **Nested stacks** are supported via a leaf-first per-stack IMPORT loop
587
- (AWS rejects `--include-nested-stacks` for IMPORT changesets).
588
- **Some resource types CloudFormation cannot import at all** (`AWS::Glue::Table`,
589
- `AWS::Route53::RecordSet` / `::RecordSetGroup`, `AWS::AppSync::ApiKey`,
590
- `AWS::EC2::NetworkAclEntry`, `AWS::SQS::QueuePolicy`,
591
- `AWS::SNS::TopicPolicy`, …). cdkd detects those from the resource type's
592
- CloudFormation registry schema and names every affected resource at once —
593
- before acquiring the stack lock, and before submitting anything — so you fix
594
- them in one pass instead of one per re-run.
595
- `--skip-import-support-preflight` bypasses the check if AWS has since made a
596
- type importable.
597
-
598
- See **[docs/import.md](docs/import.md)** for the full guide — Custom Resource
599
- 2-phase flow, nested-stack adoption mechanics (`--cfn-child-stack-name`
600
- per-child overrides, AWS's "Nest an existing stack" pattern), and the
601
- design rationale at [docs/design/464-nested-stacks-export-import.md](docs/design/464-nested-stacks-export-import.md).
602
-
603
- ## Reference CloudFormation-managed stacks (mixed estates)
604
-
605
- You don't have to migrate a producer stack to reference it. When an
606
- `Fn::ImportValue` / `Fn::GetStackOutput` reference is not found in cdkd
607
- state, cdkd falls back to CloudFormation — `ListExports` for
608
- `Fn::ImportValue` (CFn's own semantic for the intrinsic), the stack's
609
- outputs via `DescribeStacks` for `Fn::GetStackOutput` — so a
610
- cdkd-deployed stack can consume values from a stack still managed by
611
- `cdk deploy` / raw CloudFormation, with zero changes on the producer
612
- side.
613
-
614
- This makes the recommended split work out of the box: shared
615
- infrastructure (VPC, domains, IAM) stays on the CDK CLI, while dev/test
616
- app stacks iterate via cdkd.
617
-
618
- ```typescript
619
- // Producer: deployed with `cdk deploy` (stays CloudFormation-managed).
620
- new cdk.CfnOutput(this, 'SharedVpcId', {
621
- value: vpc.vpcId,
622
- exportName: 'SharedVpcId',
623
- });
624
-
625
- // Consumer: deployed with `cdkd deploy`. Resolution order is cdkd state
626
- // first, then CloudFormation — same syntax either way.
627
- const vpcId = cdk.Fn.importValue('SharedVpcId');
628
- ```
629
-
630
- How it behaves:
631
-
632
- - **cdkd-first precedence.** The fallback fires only after cdkd state
633
- misses, so cdkd-to-cdkd references are untouched and a name collision
634
- resolves to the cdkd export.
635
- - **Weak reference.** A CFn-sourced value is not recorded into cdkd
636
- state, and neither engine blocks deleting the CFn producer while cdkd
637
- consumers reference it (CloudFormation's export-in-use protection
638
- cannot see cdkd consumers). Check downstream consumers before deleting
639
- a producer.
640
- - **IAM**: the deploying credentials need `cloudformation:ListExports` /
641
- `cloudformation:DescribeStacks` for the fallback. Without them, cdkd
642
- warns and fails with the ordinary not-found error.
643
- - **Opt-out**: `--no-cfn-fallback` (on `deploy` / `diff`) pins
644
- cdkd-state-only resolution — minimal IAM, and an export-name typo
645
- fails fast instead of matching an unrelated CloudFormation export.
646
- - This also works mid-migration: after `cdkd export` hands a producer
647
- back to CloudFormation, remaining cdkd consumers keep resolving its
648
- outputs through the fallback (no leaf-first ordering requirement).
649
-
650
- See **[docs/cross-stack-references.md](docs/cross-stack-references.md)**
651
- for the full design (resolution order, weak-vs-strong reference
652
- semantics, cross-region / cross-account forms).
653
-
654
- ## Drift detection
655
-
656
- `cdkd drift` (state-driven; no synth) compares each managed resource
657
- against AWS reality and reports divergence — including console-side
658
- changes to keys you did NOT template (S3 public-access-block, IAM Role
659
- tags, Lambda env keys, etc.).
660
-
661
- ```bash
662
- cdkd drift # auto-detect single stack, exit 1 if drift
663
- cdkd drift MyStack --json # machine-readable, for CI gating
664
- cdkd drift MyStack --accept --yes # state ← AWS (catch up after a console edit)
665
- cdkd drift MyStack --revert --yes # AWS ← state (undo a console edit)
666
- cdkd state refresh-observed MyStack # populate the drift baseline without redeploying
667
- ```
668
-
669
- See **[docs/cli-reference.md `cdkd drift`](docs/cli-reference.md#cdkd-drift)**
670
- for the full reference: `--no-capture-observed-state` deploy opt-out
671
- (per-command vs per-project, mid-flight reversibility), v2→v3 state
672
- upgrade flow, exit codes, and what changes when capture is off.
673
-
674
- ## Orphan vs destroy
675
-
676
- `destroy` deletes the AWS resources **and** the state record;
677
- `orphan` deletes **only** the state record (AWS resources stay
678
- intact, just no longer tracked by cdkd). Mirrors aws-cdk-cli's
679
- `cdk orphan`.
680
-
681
- Two `orphan` variants at different granularities:
682
-
683
- - `cdkd orphan <constructPath>...` — synth-driven, **per-resource**.
684
- Rewrites every sibling reference (Ref / Fn::GetAtt / Fn::Sub /
685
- dependencies) so the next deploy doesn't re-create the orphan.
686
- - `cdkd state orphan <stack>...` — state-driven, **whole-stack**.
687
- Removes the entire state record. Works without the CDK app.
688
-
689
- Both `cdkd destroy` (synth-driven) and `cdkd state destroy`
690
- (state-driven, no synth) delete AWS resources + state.
691
-
692
- ## VPC route DependsOn relaxation (on by default)
693
-
694
- CDK injects defensive `DependsOn` from VPC Lambdas onto private-subnet
695
- routes. The dependency is real at runtime but NOT required at deploy
696
- time. cdkd drops it by default so CloudFront + Lambda::Url propagation
697
- runs in parallel with NAT stabilization (~50% faster on VPC+Lambda+CloudFront
698
- stacks; bench-cdk-sample 398s → 181s). Pass
699
- `cdkd deploy --no-aggressive-vpc-parallel` to opt out (e.g. when a
700
- Custom Resource synchronously invokes a VPC Lambda outside cdkd's
701
- Lambda-ServiceToken Active wait).
702
-
703
- See [docs/cli-reference.md](docs/cli-reference.md) for the full
704
- type-pair allowlist and trade-off notes.
705
-
706
- ## `DeletionPolicy: Snapshot`: final snapshots on delete
707
-
708
- Matching CloudFormation, cdkd creates a **final snapshot before deleting**
709
- a resource whose `DeletionPolicy` is `Snapshot` (the CDK RDS L2 defaults
710
- `removalPolicy` to `SNAPSHOT`) — and, on replacements, one whose
711
- `UpdateReplacePolicy` is `Snapshot`: RDS DBInstance / DBCluster,
712
- Neptune / DocDB clusters and ElastiCache CacheCluster delete via the API's
713
- atomic final-snapshot parameter; EC2 Volumes, Redshift Clusters and
714
- ElastiCache ReplicationGroups get a pre-delete snapshot waited to
715
- completion. The full CFn-documented Snapshot-capable type list is covered.
716
- Pass `--skip-final-snapshot` (on `deploy` / `destroy` / `state destroy` /
717
- `rollback`) to delete without the snapshot (explicit data-loss opt-out for
718
- dev/test stacks). Rolling a CREATE back is a delete too, so the policy
719
- applies there as well — for a resource whose CREATE completed and, under
720
- `--revert-failed`, for one whose CREATE failed after AWS provisioned it. See the "DeletionPolicy: Snapshot" section in
721
- [docs/cli-reference.md](docs/cli-reference.md).
722
-
723
- ## `--remove-protection`: one-shot bypass for protected resources
724
-
725
- `cdkd destroy --remove-protection` (and `cdkd state destroy --remove-protection`)
726
- flips every protection flag off in-place before each provider's delete
727
- API call, so a destroy proceeds without an intermediate edit / redeploy.
728
- Covers stack-level `terminationProtection` (logged as a WARN) AND
729
- resource-level protection on these types:
730
-
731
- | Resource type | Protection field |
732
- | --- | --- |
733
- | `AWS::Logs::LogGroup` | `DeletionProtectionEnabled` |
734
- | `AWS::RDS::DBInstance` | `DeletionProtection` |
735
- | `AWS::RDS::DBCluster` | `DeletionProtection` |
736
- | `AWS::DocDB::DBCluster` | `DeletionProtection` (DocDB DBInstance has no `DeletionProtection` field, so per-instance bypass is a no-op) |
737
- | `AWS::Neptune::DBCluster` | `DeletionProtection` |
738
- | `AWS::Neptune::DBInstance` | `DeletionProtection` |
739
- | `AWS::DynamoDB::Table` | `DeletionProtectionEnabled` |
740
- | `AWS::DynamoDB::GlobalTable` | `DeletionProtectionEnabled` (CDK v2 `dynamodb.TableV2`) |
741
- | `AWS::EC2::Instance` | `DisableApiTermination` |
742
- | `AWS::ElasticLoadBalancingV2::LoadBalancer` | attribute `deletion_protection.enabled` |
743
- | `AWS::Cognito::UserPool` | `DeletionProtection` (`ACTIVE` / `INACTIVE`) |
744
- | `AWS::AutoScaling::AutoScalingGroup` | `DeletionProtection` (`none` / `prevent-force-deletion` / `prevent-all-deletion`) — flag also sets `ForceDelete: true` so AWS terminates running instances as part of the delete |
745
- | `AWS::DSQL::Cluster` | `DeletionProtectionEnabled` (flipped via a Cloud Control `UpdateResource` patch before delete) |
746
- | `AWS::NeptuneGraph::Graph` | `DeletionProtection` (same Cloud Control patch flip) |
747
- | `AWS::SMSVOICE::ProtectConfiguration` | `DeletionProtectionEnabled` (same Cloud Control patch flip) |
748
- | `AWS::VerifiedPermissions::PolicyStore` | `DeletionProtection` `{Mode}` (patched to `{Mode: DISABLED}`) |
749
- | `AWS::EKS::Cluster` | `DeletionProtection` (same Cloud Control patch flip) |
750
- | `AWS::RDS::GlobalCluster` | `DeletionProtection` (same Cloud Control patch flip) |
751
- | `AWS::DocDB::GlobalCluster` | `DeletionProtection` (same Cloud Control patch flip) |
752
-
753
- A single `--remove-protection` covers every type listed above (no
754
- per-type variant). The interactive confirm prompt switches to
755
- `y/N` (requiring an explicit `y` for the destructive bypass);
756
- `--yes` / `-y` / `-f` skips it.
757
-
758
- Out of scope: types where AWS doesn't expose a synchronous "flip
759
- protection off" API call (CloudFront Distributions, Lambda function
760
- reserved concurrency, S3 bucket retention, etc.).
761
-
762
- ## `publish-assets`: synth + build + publish, no deploy
763
-
764
- `cdkd publish-assets` runs the asset half of the deploy pipeline
765
- only — synthesize, build Docker images, upload file assets to S3,
766
- push images to ECR — and stops. No state writes, no provisioning.
767
- Typical CI split where one runner builds + uploads assets and a
768
- separate runner deploys.
769
-
770
- ```bash
771
- cdkd publish-assets # all stacks (or auto-detect single stack)
772
- cdkd publish-assets MyStack # specific stack
773
- cdkd publish-assets -a cdk.out # skip synth, use pre-synthesized assembly
774
- ```
775
-
776
- See [docs/cli-reference.md](docs/cli-reference.md#publish-assets-synth--build--publish-no-deploy)
777
- for stack-selection rules and concurrency knobs.
778
-
779
- ## Compatibility
780
-
781
- cdkd supports the standard CloudFormation surface — intrinsic functions,
782
- pseudo parameters, parameters / conditions, cross-stack / cross-region
783
- references, asset publishing, custom resources, and so on. Cross-stack
784
- references also work against producer stacks still managed by
785
- CloudFormation (see
786
- ["Reference CloudFormation-managed stacks"](#reference-cloudformation-managed-stacks-mixed-estates)
787
- above). See
788
- **[docs/supported-features.md](docs/supported-features.md)** for the
789
- full reference. For per-resource-type provisioning support (SDK Providers
790
- vs Cloud Control API fallback), see
791
- **[docs/supported-resources.md](docs/supported-resources.md)**.
792
-
793
- **Property-level coverage is incremental.** SDK Providers wire most but not every CFn property of a supported type. cdkd fails fast at pre-flight when a template uses a not-yet-implemented property, with the property name + a 1-click issue link. `--allow-unsupported-properties <Type>:<Prop>,...` is the safety valve when this is too strict (e.g. mid-life update on an existing resource); avoid it on security-meaningful properties (encryption / IAM / TLS). See [docs/cli-reference.md](docs/cli-reference.md#--allow-unsupported-properties-deploy).
794
-
795
- ## State Management
796
-
797
- State is stored in S3 with optimistic locking via S3 Conditional Writes
798
- (no DynamoDB required). Keys are scoped by `(stackName, region)` so the
799
- same stack deployed to two regions has two independent state files.
800
-
801
- **You do not create this bucket yourself** — `cdkd bootstrap` creates it once
802
- per account (see [Prerequisites](#prerequisites)), with versioning, AES-256
803
- encryption, and a deny-external-access bucket policy. The settings below are
804
- only for pointing cdkd at a non-default name; pass that same name to
805
- `cdkd bootstrap --state-bucket` so bootstrap creates it for you there too.
806
-
807
- | Setting | CLI | cdk.json | Env var | Default |
808
- |---------|-----|----------|---------|---------|
809
- | Bucket | `--state-bucket` | `context.cdkd.stateBucket` | `CDKD_STATE_BUCKET` | `cdkd-state-{accountId}` (legacy `cdkd-state-{accountId}-{region}` is still read with a deprecation warning — run `cdkd state migrate` to consolidate) |
810
- | Prefix | `--state-prefix` | - | - | `cdkd` |
811
-
812
- The state bucket is shared across all CDK apps in the same account by
813
- default. To isolate apps, pass different `--state-prefix` values.
814
- `cdkd destroy --all` only targets stacks from the current CDK app
815
- (determined by synthesis), not all stacks in the bucket.
816
-
817
- See **[docs/state-management.md](docs/state-management.md)** for the full
818
- spec: S3 key layout, optimistic-locking mechanism (ETag-based), state
819
- schema, legacy `version: 1` migration, bucket-name migration via
820
- `cdkd state migrate`, and troubleshooting.
821
-
822
- ## Deployment events (`cdkd events`)
823
-
824
- Every `cdkd deploy` / `cdkd destroy` run records a structured event
825
- stream to S3 — cdkd's local equivalent of CloudFormation's
826
- `DescribeStackEvents`. Read it back with `cdkd events <stack>`:
827
-
828
- ```bash
829
- cdkd events MyStack # list runs, newest first
830
- cdkd events MyStack --run <runId> # one run's full event stream
831
- cdkd events MyStack --format json # machine-readable (AI-agent hand-off)
832
- cdkd events prune MyStack --all # purge event history (reclaim S3 space)
833
- cdkd destroy MyStack --purge-events # destroy + purge events in one command
834
- ```
835
-
836
- Events are persisted as JSONL under a `deployments/` key family separate
837
- from `state.json` (no state schema bump), so a destroyed stack's failure
838
- history stays readable. Recording is best-effort and never blocks the
839
- run; events carry error + metadata only (never resource properties). The
840
- store self-bounds to the last 20 runs, `cdkd events prune` purges old
841
- history on demand (`--keep N` / `--older-than <dur>` / `--all`), and
842
- `cdkd destroy --purge-events` deletes a stack's history right after a clean
843
- destroy so the bucket returns fully empty. See
844
- **[docs/deployment-events.md](docs/deployment-events.md)** for the full
845
- reference.
846
-
847
- ## Stack Outputs
848
-
849
- CDK's `CfnOutput` constructs are resolved and stored in the state file:
850
-
851
- ```typescript
852
- // In your CDK code
853
- new cdk.CfnOutput(this, 'BucketArn', {
854
- value: bucket.bucketArn, // Uses Fn::GetAtt internally
855
- description: 'ARN of the bucket',
856
- });
857
- ```
858
-
859
- After deployment, outputs are resolved and printed at the end of `cdkd deploy` (matching CDK CLI's format) and saved to the S3 state file:
860
-
861
- ```text
862
- Deployment Summary:
863
- Stack: MyStack
864
- ...
865
- Duration: 21.25s
866
-
867
- Outputs:
868
- MyStack.BucketArn = arn:aws:s3:::actual-bucket-name-xyz
869
-
870
- ✓ Deployment completed successfully
871
- ```
872
-
873
- ```json
874
- {
875
- "outputs": {
876
- "BucketArn": "arn:aws:s3:::actual-bucket-name-xyz"
877
- }
878
- }
879
- ```
880
-
881
- **Key differences from CloudFormation**:
882
-
883
- - CloudFormation: Outputs accessible via `aws cloudformation describe-stacks`
884
- - cdkd: Outputs saved in S3 state file (e.g., `s3://bucket/cdkd/MyStack/us-east-1/state.json`)
885
- - Both print outputs to stdout after a successful deploy
886
- - Both resolve intrinsic functions (Ref, Fn::GetAtt, etc.) to actual values
887
-
888
- ## Exit codes
889
-
890
- cdkd commands distinguish three outcomes via the process exit code so
891
- CI / bench scripts can react without grepping log output:
892
-
893
- | Exit | Meaning |
894
- |------|---------|
895
- | `0` | Success — command completed and no resources are in an error state |
896
- | `1` | Command-level failure — auth error, bad arguments, synth crash, unhandled exception |
897
- | `2` | **Partial failure** — work completed but one or more resources failed or was SKIPPED (state.json is preserved, re-running typically resolves it) |
898
-
899
- Exit `2` is emitted by `cdkd destroy` and `cdkd state
900
- destroy` when one or more per-resource deletes fail. The summary line
901
- also switches from `✓ Stack X destroyed` to `⚠ Stack X partially
902
- destroyed (...). State preserved — re-run 'cdkd destroy' / 'cdkd
903
- state destroy' to clean up.` so the visual marker matches the exit
904
- code.
905
-
906
- A per-resource **skip** exits `2` as well: `⚠ MyTable (AWS::Glue::Table)
907
- skipped (...)` plus `(4 deleted, 1 skipped, 0 errors)` means cdkd could not
908
- address that resource, issued no AWS call, and therefore left it in place —
909
- so the state record is deliberately KEPT rather than dropped. See
910
- [docs/cli-reference.md](docs/cli-reference.md#skipped-resources-on-destroy-issue-1752).
911
-
912
- One figure on that line does **not** mean exit `2`: `N unverified` (issue
913
- [#2301](https://github.com/go-to-k/cdkd/issues/2301)). It counts pre-flight
914
- safety guards that ran, could not reach a verdict, and were therefore not
915
- enforced — cdkd proceeded, and the resource really was deleted, so
916
- `(1 deleted, 1 unverified, 0 errors)` exits `0`. It is there because such a
917
- guard is disabled by DENYING the permission its probe needs, and the warning
918
- cdkd prints does not survive the run; the durable half is a
919
- `RESOURCE_GUARD_INDETERMINATE` event
920
- ([docs/deployment-events.md](docs/deployment-events.md)).
921
-
922
- `cdkd deploy` exits `2` for the same class of outcome (issue
923
- [#1960](https://github.com/go-to-k/cdkd/issues/1960)) — it finished, nothing
924
- failed, and yet a resource cdkd was responsible for may still be alive in AWS:
925
-
926
- | Summary row | What survived | Recovers on its own? |
927
- |---|---|---|
928
- | `Skipped (not deleted): N` | a resource removed from the template whose provider could not issue the delete | **Yes** — the state record is kept, so the next `cdkd deploy` re-attempts it |
929
- | `of which left an orphaned predecessor: N` | the OLD resource of a replacement (e.g. an ACM certificate a CloudFront distribution still references) | **No** — state now points at the replacement, so nothing will retry it; delete it by hand |
930
-
931
- A run that left a resource unaddressed also stops printing
932
- `✓ Deployment completed successfully` — it prints a `⚠ Stack X deployed, but N
933
- resource(s) were left unaddressed` warning instead, and records
934
- `result: 'FAILED'` in `cdkd events` (matching destroy). **A pipeline grepping
935
- the log for the success string breaks on this independently of the exit code.**
936
-
937
- Pass `--allow-unaddressed` to exit `0` instead. The summary rows, the
938
- per-resource warnings, the `⚠` banner and the `FAILED` run record all survive
939
- it, because they record what happened rather than what the operator chose to
940
- tolerate. It does suppress the run-level error message along with the exit code
941
- — that message is the only place the "delete it by hand" remedy appears — and
942
- the banner's closing sentence changes to say the flag was passed. See
943
- [docs/cli-reference.md](docs/cli-reference.md#--allow-unaddressed-deploy). The flag exists because the
944
- orphaned-predecessor case can be temporarily unfixable (an ACM replacement
945
- blocked on `DescribeCertificate.InUseBy` clears once the consumer finishes
946
- updating). Prefer it over a shell `|| [ $? -eq 2 ]` wrapper, which would also
947
- swallow the unrelated failures that exit `2` — macro expansion errors and
948
- unsupported in-place updates.
949
-
950
- ## Local execution
951
-
952
- The `cdkd local` family runs AWS workloads on the developer's machine
953
- via Docker — Lambda functions, API Gateway routes, ECS tasks, and
954
- long-running ECS services — without an AWS deploy. Modeled on `sam local *` but reuses cdkd's
955
- synthesis / asset / construct-path plumbing — no `template.yaml` to
956
- maintain, no `cdk synth | sam ...` round-trip.
957
-
958
- | Subcommand | Emulates |
959
- | --- | --- |
960
- | `cdkd local invoke <target>` | One-shot Lambda invoke via the AWS Lambda Runtime Interface Emulator (RIE) |
961
- | `cdkd local start-api` | Long-running HTTP server for REST v1 / HTTP API / Function URL routes |
962
- | `cdkd local run-task <target>` | ECS RunTask — every container in a task definition started on a per-task docker network |
963
- | `cdkd local start-service <target>` | Long-running ECS Service emulator — `DesiredCount` replicas with restart-on-exit (no local load balancer in v1) |
964
- | `cdkd local invoke-agentcore <target>` | One-shot Bedrock AgentCore Runtime invoke (HTTP `/invocations` / MCP `/mcp` / A2A `/a2a` / AGUI / WebSocket `--ws`) |
965
- | `cdkd local start-agentcore [target]` | Long-running serve of a Bedrock AgentCore Runtime against a warm container (all four protocols): HTTP / AGUI serve `POST /invocations` + `GET /ping` plus the `/ws` bridge (injects the session-id / Authorization a header-less browser client cannot set); MCP serves `/mcp`, A2A serves `/`. `--sigv4` / `--watch` supported |
966
- | `cdkd local start-alb <targets...>` | Long-running local ALB front-door (HTTP + HTTPS listeners, path / host / header / weighted / redirect / fixed-response routing, authenticate-cognito / authenticate-oidc) for ECS / Lambda backing services |
967
- | `cdkd local start-cloudfront [target]` | Long-running local CloudFront distribution — viewer-request -> S3 / Lambda Function URL origin -> viewer-response pipeline, CloudFront Functions run in-process (Function URL origins use Docker/RIE) |
968
-
969
- The Docker-backed commands above require Docker. Pass `--from-state`
970
- (cdkd-deployed) or `--from-cfn-stack` (cdk-deployed / CFn-managed) to
971
- substitute deployed physical IDs into intrinsic-valued env vars /
972
- secrets / image URIs; without either, intrinsic values are dropped with
973
- a per-key warning (matches `sam local *`). The two flags are mutually
974
- exclusive. `start-cloudfront` carries both `--from-state` and
975
- `--from-cfn-stack` too (since cdk-local 0.128.0 / issue #766); a
976
- CloudFront-Functions + S3-origin distribution still serves entirely
977
- in-process (no Docker), while a Lambda Function URL origin runs via the
978
- RIE container.
979
-
980
- ### `local invoke`
981
-
982
- ```bash
983
- cdkd local invoke MyStack/Handler # one-shot invoke
984
- cdkd local invoke MyStack/Handler --event events/get.json
985
- cdkd local invoke MyStack/Handler --from-state # OR --from-cfn-stack
986
- ```
987
-
988
- All AWS Lambda runtimes (Node.js / Python / Ruby / Java / .NET /
989
- `provided.al2023`), ZIP and container Lambdas, same-stack Lambda Layers
990
- bind-mounted at `/opt`.
991
-
992
- ### `local start-api`
993
-
994
- ```bash
995
- cdkd local start-api # one HTTP server per discovered API
996
- cdkd local start-api MyStack/MyHttpApi --watch # filter + hot reload
997
- cdkd local start-api --from-state # OR --from-cfn-stack
998
-
999
- # Typical shape — the bare `--from-cfn-stack` flag auto-resolves to the
1000
- # routed stack's name (here `MyStack`). Pass an explicit value only when
1001
- # the deployed CFn stack name differs from the CDK stack name.
1002
- cdkd local start-api MyStack/MyHttpApi --from-cfn-stack
1003
- ```
1004
-
1005
- REST v1 + HTTP API v2 + Function URL with all integration kinds
1006
- (AWS_PROXY / MOCK / HTTP_PROXY / HTTP / AWS Lambda non-proxy via
1007
- hand-rolled VTL), authorizers (Lambda / Cognito / HTTP v2 JWT /
1008
- AWS_IAM SigV4 on REST v1 + Function URL), CORS, stage variables,
1009
- `--watch` hot reload.
1010
-
1011
- ### `local run-task`
1012
-
1013
- ```bash
1014
- cdkd local run-task MyStack/MyService/TaskDef
1015
- cdkd local run-task MyTaskDef --from-state # OR --from-cfn-stack
1016
- ```
1017
-
1018
- Every container in the task definition on a per-task docker network
1019
- with the AWS-published ECS metadata sidecar.
1020
-
1021
- ### `local start-service`
1022
-
1023
- ```bash
1024
- cdkd local start-service MyStack/Orders MyStack/Web # multiple services in one invocation
1025
- cdkd local start-service MyStack/Orders --from-state # OR --from-cfn-stack
1026
- cdkd local start-service MyStack/Web --watch # hot reload (sub-second on interpreted handlers)
1027
- ```
1028
-
1029
- Long-running ECS Service emulator: `DesiredCount` replicas with
1030
- restart-on-exit, cross-service Service Connect / Cloud Map DNS
1031
- discovery (peer containers reach each other by `<discoveryName>.<namespace>`).
1032
- No local load-balancer in v1. `--watch` re-synths on every CDK source edit
1033
- and reloads one replica at a time — source-only edits on
1034
- interpreted-language handlers (Node / Python / Ruby / shell) take a
1035
- bind-mount fast path (`docker cp` + `docker restart`; no rebuild);
1036
- Dockerfile / dependency manifest / compiled-language source edits fall
1037
- through to a full rebuild + shadow boot + atomic swap.
1038
-
1039
- ### `local start-alb`
1040
-
1041
- ```bash
1042
- cdkd local start-alb MyStack/MyAlb --lb-port 80=8080 # remap privileged listener port
1043
- cdkd local start-alb MyStack/MyAlb --from-state # OR --from-cfn-stack
1044
- cdkd local start-alb MyStack/MyAlb --watch # hot reload (sub-second on interpreted handlers)
1045
- ```
1046
-
1047
- Long-running local ALB front-door: names an `AWS::ElasticLoadBalancingV2::LoadBalancer`,
1048
- boots every ECS service behind its listeners, and stands up a local
1049
- HTTP / HTTPS front-door on each listener port that round-robins across
1050
- the running replicas and routes its listener rules across the backing
1051
- services. Forward / redirect / fixed-response actions; ECS or Lambda
1052
- targets; authenticate-cognito / authenticate-oidc via a local Bearer-JWT
1053
- check. `--watch` reloads one backing-replica at a time across edits —
1054
- interpreted-handler source edits go through the bind-mount fast path
1055
- (no rebuild); Dockerfile / dependency / compiled-source edits fall
1056
- through to a rebuild + atomic front-door pool swap.
1057
-
1058
- ### `local start-cloudfront`
1059
-
1060
- ```bash
1061
- cdkd local start-cloudfront # interactive picker
1062
- cdkd local start-cloudfront MyStack/MyDistribution # name the distribution
1063
- cdkd local start-cloudfront MyStack/MyDistribution --watch # re-synth + swap on edit
1064
- cdkd local start-cloudfront MyStack/MyDistribution --tls # real HTTPS termination
1065
- ```
1066
-
1067
- Serves a CloudFront distribution's **viewer-request -> S3 origin ->
1068
- viewer-response** pipeline locally so a routing-function change is
1069
- verifiable in seconds instead of a deploy round-trip. The distribution's
1070
- `AWS::CloudFront::Function`s (URL rewrites, trailing-slash normalization,
1071
- SPA fallback, header tweaks) run in-process in a `node:vm` sandbox; the
1072
- S3 origin content is the `BucketDeployment` source asset resolved out of
1073
- the cloud assembly, served with `DefaultRootObject` and
1074
- `CustomErrorResponses`. Path patterns route across the default + ordered
1075
- cache behaviors. Pure-local: no Docker, no AWS call — `--watch` is just
1076
- re-synth + an in-memory routing-model swap. S3 origins only (custom /
1077
- Lambda@Edge origins are warn-and-skip); `--origin <id>=<dir>` points an
1078
- origin at a local directory when `BucketDeployment` resolution can't.
150
+ Cold end-to-end wall clock including synth / plan, median of 7 runs, seconds, `us-east-1`.
1079
151
 
1080
- See **[docs/local-emulation.md](docs/local-emulation.md)** for the
1081
- full reference — runtimes, target resolution, every flag, integration
1082
- and authorizer detail, route precedence, container pool, networking,
1083
- `--from-cfn-stack` semantics, v1 scope.
152
+ Full methodology, distribution analysis, per-run data, and the complete suite (SDK Provider path, Cloud Control fallback path, the 15x headline stack): **[cdkd.dev/benchmarks/](https://cdkd.dev/benchmarks/)**.
1084
153
 
1085
154
  ## License
1086
155