AWS 229: Deploy a stack with CloudFormation and AWS CDK, then inspect drift
Why this lesson matters
AWS CDK does not replace CloudFormation. CDK source builds a construct tree and synthesizes a CloudFormation template. CloudFormation creates, updates, detects drift in, and deletes the stack. An architect must inspect every boundary instead of treating cdk deploy as magic.
This pinned JavaScript project creates only a seven-day CloudWatch Logs log group and a non-secret Systems Manager Parameter Store string. You will synthesize, inspect, optionally deploy, create reversible drift, reconcile it through source, and prove cleanup.
What you will be able to do
- Distinguish an app, construct, CDK stack, template, asset, CloudFormation
stack, and deployed resource.
- Explain L1, L2, and L3 constructs, context, lookups, and unresolved tokens.
- Pin dependencies and run assertions before AWS access.
- Inspect bootstrap roles, trust, policies, S3/ECR stores, and ownership.
- Compare synth, diff, change sets, stack events, and drift detection.
- Adopt or restore drift through reviewed source and prove exact cleanup.
Safety and approval boundary
- Local tests and synthesis create no AWS resource. Live work needs account-owner
approval and an exact account/Region check before every mutation.
- Use a normal role, never root or keys in files. The lab parameter is non-secret;
String is not a secret store.
- Never bootstrap automatically or delete
CDKToolkit; it is shared,
security-sensitive infrastructure.
- Never hide security changes with
--require-approval never. - Use only context
environment=lab,dev, ortest; the app rejects others. - Redact account IDs and full ARNs from shared evidence.
The complete mental model
lockfile + JavaScript + context
-> CDK app/construct tree
-> cdk synth
-> template + manifest + any assets
-> tests/review/cdk diff
-> CloudFormation change set/events
-> Logs log group + SSM parameter
-> drift -> source reconciliation -> destroy
There is one nw-p11-cdk-lab application stack. “CDK stack” is its source model; “CloudFormation stack” is its deployed lifecycle - not a second copy.
| Term | Meaning | Review focus |
|---|---|---|
| App | Top-level CDK program | context, account/Region, stack instances |
| Construct | Component in the tree | defaults and generated child resources |
| L1 | Near-direct CloudFormation resource (Cfn*) | explicit properties |
| L2 | Intent-based AWS resource abstraction | defaults and helpers |
| L3/pattern | Multi-resource architecture | all transitive resources/IAM |
| Token | Deployment-time value | resulting Ref, Fn::GetAtt, pseudo parameter |
| Context | Synthesis input/cache | reproducibility and secret risk |
| Asset | Uploaded file/container | hash, store, retention, ownership |
| Cloud assembly | cdk.out templates/manifests | exact deployable output |
| Drift | Supported actual property differs from expected template | property differences/time |
Context/lookups can make synthesis environment-dependent. Commit only reviewed, non-secret context when reproducibility needs it. This project has no lookup, asset, IAM resource, network, compute, bucket, repository, or public endpoint.
Download and verify the reviewed project
Download the complete locked project, or inspect package.json, the lockfile, stack source, and assertions.
Pins: CDK CLI 2.1141.0, aws-cdk-lib 2.269.0, constructs 10.8.1. The CLI and library now have independent release numbers. Use Node.js 22+ and the lockfile-controlled local CLI:
tar -xzf nw-p11-cdk-drift.tar.gz
cd p11-cdk-drift
node --version
npm ci
npm test
npx cdk --version
npx cdk synth --quiet
Do not use sudo npm to bypass permissions. Inspect cdk.out/nw-p11-cdk-lab.template.json and require:
- one log group
/nw/p11/cdk/lab/application, retention 7; - one Standard SSM String parameter
/nw/p11/cdk/lab/owner; Deletedeletion and update-replacement policies on both data resources;- exact outputs and project/environment/cleanup tags;
- no IAM, network, compute, S3, ECR, public endpoint, or secret.
AWS::CDK::Metadata may also appear; it is not application data. Assertions prove selected invariants, not permissions, quotas, runtime behavior, or an absence of unexpected resources outside their checks - review still matters.
Bootstrap is a separate security decision
Modern bootstrapping can create an asset bucket, ECR repository, IAM roles, an SSM version parameter, and optionally a KMS key. Role trust and execution policy can grant broad cross-account deployment power. Inspect first:
export AWS_DEFAULT_REGION="ap-south-1"
aws sts get-caller-identity --output json
aws cloudformation describe-stacks --stack-name CDKToolkit --output json
aws cloudformation get-template --stack-name CDKToolkit \
--template-stage Processed --output json
aws cloudformation list-stack-resources --stack-name CDKToolkit --output table
If absent/incompatible, stop. Bootstrap only through the platform owner's approved trust, policies, boundary, qualifier, encryption, and termination protection design. This lesson does not authorize bootstrap or its deletion.
Optional live deployment
1. Freeze target and preflight
export AWS_DEFAULT_REGION="ap-south-1"
export CDK_DEFAULT_REGION="$AWS_DEFAULT_REGION"
export CDK_DEFAULT_ACCOUNT="$(aws sts get-caller-identity --query Account --output text)"
aws sts get-caller-identity --output json
aws configure list
npm test
npx cdk synth --quiet
npx cdk diff nw-p11-cdk-lab
Privately match the account with approval. Save the template/diff; reject any unexplained replacement, IAM broadening, public access, or extra resource.
2. Deploy and inspect CloudFormation
npx cdk deploy nw-p11-cdk-lab --require-approval broadening \
--outputs-file cdk-outputs.json
aws cloudformation describe-stacks --stack-name nw-p11-cdk-lab --output json
aws cloudformation describe-stack-events --stack-name nw-p11-cdk-lab \
--max-items 50 --output table
aws cloudformation list-stack-resources --stack-name nw-p11-cdk-lab --output table
Require CREATE_COMPLETE, exact resources/outputs/tags, and no failed event. A successful CLI exit does not prove workload read/write behavior.
log_group="$(jq -r '."nw-p11-cdk-lab".LogGroupName' cdk-outputs.json)"
parameter_name="$(jq -r '."nw-p11-cdk-lab".OwnerParameterName' cdk-outputs.json)"
aws logs describe-log-groups --log-group-name-prefix "$log_group" --output json
aws ssm get-parameter --name "$parameter_name" \
--query 'Parameter.[Name,Type,Value,Version]' --output table
Require retention 7 and parameter String/NitWings-P11. Preserve output names.
Create and detect reversible drift
Drift compares supported actual properties with CloudFormation's expected template. It is asynchronous; unsupported/unmodelled state can still differ.
aws logs put-retention-policy --log-group-name "$log_group" --retention-in-days 14
drift_id="$(aws cloudformation detect-stack-drift \
--stack-name nw-p11-cdk-lab --query StackDriftDetectionId --output text)"
while true; do
status="$(aws cloudformation describe-stack-drift-detection-status \
--stack-drift-detection-id "$drift_id" --query DetectionStatus --output text)"
printf '%s %s\n' "$(date -u +%FT%TZ)" "$status"
case "$status" in DETECTION_COMPLETE) break;; DETECTION_FAILED) exit 1;; esac
sleep 10
done
aws cloudformation describe-stack-resource-drifts \
--stack-name nw-p11-cdk-lab \
--stack-resource-drift-status-filters MODIFIED DELETED NOT_CHECKED --output json
Require log-group MODIFIED, expected 7, actual 14. A console refresh before detection completes is not evidence.
Reconcile through source
Ordinary CDK diff compares desired templates; unchanged source may show no change while drift detection sees changed actual state.
- Change
ONE_WEEKtoTWO_WEEKSand assertion 7 to 14. - Test, synth, diff, review, deploy. This adopts 14 as desired state.
- Detect again and require
IN_SYNC. - Restore source/assertion to one week/7; test, synth, diff, deploy.
- Detect once more; require
IN_SYNCand actual retention 7.
Do not leave console state disagreeing with source. Decide which state is correct, review it in source, and let the owner tool converge it.
Diagnose failures from evidence
| Symptom | Inspect | Response |
|---|---|---|
| Synth fails | Node, lock, trace, context | Fix local runtime/source; do not deploy |
| Bootstrap error | assembly, Region, qualifier, CDKToolkit | Stop; do not bootstrap by guess |
| Cannot assume deploy role | caller, trust, policy, SCP, boundary | Fix identity path; do not grant admin |
| Rollback | earliest failed stack event, CloudTrail | Diagnose quota/name/policy/hook/service cause |
| Empty diff after manual change | drift result and synthesized template | Recognize desired-vs-actual boundary |
Drift UNKNOWN | detection status/reason | Do not claim IN_SYNC |
| Destroy fails | delete events and physical IDs | Check deny, dependency, policy, Region/account |
| Stack gone/resource remains | output, tags, policy, inventory | Prove ownership before deletion |
Save events before retries; later symptoms can hide the first failure.
Cleanup and negative proof
Restore the canonical one-week source first and save evidence outside cdk.out.
npx cdk destroy nw-p11-cdk-lab
aws cloudformation list-stacks --stack-status-filter DELETE_COMPLETE \
--query 'StackSummaries[?StackName==`nw-p11-cdk-lab`].[StackName,StackStatus]' \
--output table
aws logs describe-log-groups --log-group-name-prefix "$log_group" --output json
aws ssm get-parameters --names "$parameter_name" \
--query '[Parameters,InvalidParameters]' --output json
Require DELETE_COMPLETE, no exact log-group match, and the exact parameter in InvalidParameters. “Stack does not exist” from describe-stacks is expected after deletion. Leave CDKToolkit intact.
Cost and no-create path
CDK and ordinary CloudFormation add no separate fee, but created services do. CloudWatch Logs can charge for ingestion, storage, queries, delivery, and archive; this lab writes no events. Parameter Store Standard allowance and advanced/API charges differ. Bootstrap S3/ECR/KMS can retain charges. Check current Region pricing and use a cleanup timer.
Without AWS permission: run locked Node 22 tests/synth; inventory the template; draw bootstrap trust; explain metadata, expected-7/actual-14 drift, adoption/restoration, misleading unchanged diff, failures, and exact cleanup. Label it static evidence - it does not prove live account permissions/execution.
Lesson acceptance
Submit runtime/package/lock versions, passing assertions, template inventory, redacted target/bootstrap evidence (or no-create label), reviewed diff/events, initial state, drift ID and property difference, adoption/restoration/final IN_SYNC, and exact deletion/negative inventory. Reject unpinned-global-only work, unauthorized bootstrap, incomplete detection, console-only repair, missing cleanup proof, or a claim of live execution from static files.
Knowledge check
- Does CDK deploy beside CloudFormation? No; it synthesizes CloudFormation,
which owns deployed lifecycle.
- Why can diff be empty after manual change? It compares desired templates;
drift detection compares supported actual properties with expected state.
- Why is
CDKToolkitsensitive? It can contain shared asset stores and
trusted deployment roles with substantial authority.
- Adoption versus restoration? Adoption changes desired source to actual;
restoration changes actual state back to reviewed source intent.
- Cleanup proof?
DELETE_COMPLETEand exact negative inventory for both
output-named resources, while preserving shared bootstrap infrastructure.