Use in CI: per-PR environments
Deploy time is CI job time, and a PR environment redeploys on every push — so cdkd's speedup compounds across a PR's lifetime. Because cdkd needs zero CDK code changes, you can swap only the PR-environment workflow to cdkd and keep production / staging on the CDK CLI; switching back is a one-line workflow revert.
Run cdkd bootstrap once per AWS account beforehand (creates the state
bucket + asset storage; cdk bootstrap is not required).
One stack per PR — pass the PR number as CDK context and suffix the stack name; cdkd state is keyed by (stack name, region) and locks are per-stack, so PR environments deploy concurrently without contention:
const prNumber = app.node.tryGetContext('prNumber');
new WebAppStack(app, `WebApp${prNumber ? `-pr-${prNumber}` : ''}`);
Credentials — cdkd calls AWS APIs directly, so the deploying
identity needs permissions for every deployed resource (CDK's
cdk-hnb659fds-* roles do not work: they are designed for
CloudFormation delegation, and cdkd uses its own bootstrap storage).
Create a dedicated deploy role and switch into it with
--role-arn (or the
CDKD_ROLE_ARN env var): the workflow's OIDC base role needs only
sts:AssumeRole on the deploy role, and the deploy role's trust policy
allows only that base role — the strong permissions live in exactly one
place, reachable through one path, and never sit on the CI runner
itself.
Minimal GitHub Actions shape (deploy on open/sync/reopen, destroy on close):
on:
pull_request:
types: [opened, synchronize, reopened, closed]
permissions: { id-token: write, contents: read }
env:
CDKD_ROLE_ARN: arn:aws:iam::123456789012:role/cdkd-deploy-role
jobs:
deploy:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with: { node-version: 24, cache: npm }
- run: npm ci
- uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-base
aws-region: us-east-1
- run: npx cdkd deploy --yes -c prNumber=${{ github.event.pull_request.number }}
destroy:
if: github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-base
aws-region: us-east-1
- run: npx @go-to-k/cdkd state destroy WebApp-pr-${{ github.event.pull_request.number }} --yes
The destroy job has no checkout, npm ci, or synth —
cdkd state destroy deletes from the state
record alone, so it works even after the branch is gone.
If the environment contains protection-enabled resources (RDS /
DynamoDB deletion protection, EC2 termination protection, and more),
add --remove-protection
to the destroy so the teardown completes in one pass — an ephemeral PR
environment has nothing worth protecting, and without the flag those
resources survive the job and linger until the next sweep.
Two more teardown-completeness flags matter for disposable
environments: resources with DeletionPolicy: Snapshot leave a final
snapshot behind on every PR close by default — add
--skip-final-snapshot
when the environment's data is disposable, so snapshots don't
accumulate per closed PR. And cdkd destroy --purge-events also
removes the stack's deployment-event history so the state bucket
returns fully empty (after a state destroy, the equivalent is
cdkd events prune <stack> --all).
Housekeeping:
- Pick the wait mode from what runs next (see
wait modes): review-only environments can use
--no-wait; E2E tests after the deploy should keep the default, or--full-waitwhen they need ECS steady state / CloudFront propagation. - A job cancelled mid-deploy (e.g.
concurrency.cancel-in-progress) can leave a stack lock; it expires on its own TTL (30 minutes), or runcdkd force-unlock <stack>to clear it immediately. - Sweep forgotten environments with
cdkd state list --jsonon a schedule, and reclaim unreferenced assets withcdkd gc --older-than 30d --dry-run—gcaborts if any stack is locked, so schedule it outside deploy hours. - To comment the environment URL on the PR, read stack outputs with
cdkd state show <stack> --json. - To gate a PR without deploying,
cdkd diff --failexits1when any change is detected (andcdkd drift --jsonmachine-checks live divergence). See Exit codes for per-command semantics.