AWS CDK for operations teams
-
Phase 0: Before you start
-
Phase 1: The foundation
-
Phase 2: Operations and security
-
Phase 3: SQL Server on AWS
-
Phase 4: Troubleshooting step by step
-
Phase 5: Final project and career
A 400-line CloudFormation template is hard to read and easy to get wrong, especially the IAM parts. This AWS CDK tutorial shows the same idea in a real programming language: a few lines of TypeScript produce a complete, reviewed CloudFormation template, with least-privilege IAM written for you.
You’ll install the CDK, bootstrap your account, build a stack with a bucket, a security group in saat-vpc, an app role that can read the bucket and an SSM parameter, then diff, deploy, change and destroy it. Then you’ll fix a failed VPC lookup and a bucket that survives cdk destroy.
| Time | You need | By the end you can |
|---|---|---|
| 60 minutes | saat-vpc, your laptop signed in as saat-admin, winget (Windows 10 or 11), about 1 GB of disk for Node.js and packages. Cost: a few cents for the CDK bootstrap bucket; the demo stack itself is pennies. | Install Node.js and the CDK CLI, bootstrap an environment, write a TypeScript stack with constructs, grants and lookups, use cdk synth, diff, deploy and destroy, and handle lookup errors and retained resources. |
What it is, in one line
The AWS CDK (Cloud Development Kit) lets you define AWS infrastructure in code such as TypeScript or Python, and turns that code into CloudFormation templates it deploys for you.
In Simple Words
Ordering a thali instead of each dish
At a restaurant, you could order rice, dal, two sabzis, roti, curd, pickle and a sweet, one by one, telling the waiter how much of each. Or you order a thali, and the kitchen brings a sensible set, which you adjust: “no sweet, extra roti”.
A CloudFormation template lists every dish yourself. A CDK construct is the thali: new s3.Bucket() comes with sensible settings, and you only say what’s different.
And the bill still comes from the same kitchen. The CDK’s output is a normal CloudFormation template, so everything you learned in Lesson 34 still applies.
Engineer's View
How the CDK really works
A CDK app contains stacks, which contain constructs. L1 constructs (CfnBucket) map one-to-one to CloudFormation resources; L2 constructs (s3.Bucket) add defaults and helper methods such as grantRead(), which writes a least-privilege IAM policy; L3 constructs (patterns) build several resources together.
cdk synth runs your code and writes CloudFormation templates to cdk.out. cdk diff compares them with the deployed stack, with a separate view of IAM and security group changes. cdk deploy uploads assets and creates a change set. Before the first deploy, each account and Region must be bootstrapped: the CDKToolkit stack creates an S3 bucket, an ECR repository and roles the CDK uses.
Lookups such as Vpc.fromLookup query your account at synth time and cache results in cdk.context.json, which you commit so synth is repeatable. Removal policies matter: buckets and other stateful resources default to RETAIN, so cdk destroy leaves them behind unless you choose otherwise. The CDK CLI has its own version line (2.1000 and above since 2025), separate from the library.
How it works, step by step
Here is what happens when you run cdk deploy.
1Your code describes resources
lib/saat-cdk-stack.ts creates objects: a bucket, a security group, a role. Ordinary code, so you can use loops, conditions and functions.
2Synth writes the template
The CDK turns those objects into a CloudFormation template, adding the IAM policy for grantRead and a bucket policy for enforceSSL.
3Diff and deploy use CloudFormation
A change set is created and executed, with rollback on failure, exactly as in Lesson 34.
4Why ops teams use it
The value isn’t the language; it’s what the constructs write for you:
The trade-off: you need Node.js (or Python) and must review the synthesized template, not just the code. cdk diff is the habit that makes it safe.
✅ Checkpoint: You can explain constructs, synth, diff, deploy and bootstrap, and why lookups and removal policies need attention.
Hands-on lab: an AWS CDK tutorial in TypeScript
Install Node.js and the CDK CLI. Close and reopen PowerShell after the first line so the new PATH is picked up:
winget install OpenJS.NodeJS.LTS
npm install -g aws-cdk
node --version
cdk --versionYou should see something like this:
v22.20.0
2.1031.0 (build 3c4d5e6)1Give the CDK credentials and bootstrap
The CDK reads standard AWS credentials. Exporting the temporary credentials from your aws login session is the simplest way; they last about 15 minutes, so run these three lines again if you see ExpiredToken later.
Paste this whole block:
cd C:\aws-lab
aws login --profile saat-admin
aws configure export-credentials --profile saat-admin --format powershell | Invoke-Expression
Remove-Item Env:AWS_PROFILE -ErrorAction SilentlyContinue
$v = Get-Content C:\aws-lab\saat-vpc.json | ConvertFrom-Json
$acct = aws sts get-caller-identity --query Account --output text
cdk bootstrap "aws://$($acct)/ap-south-1" --tags course=saatvedha-awsYou should see something like this (the first time takes two or three minutes):
⏳ Bootstrapping environment aws://111122223333/ap-south-1...
✅ Environment aws://111122223333/ap-south-1 bootstrapped.Bootstrapping is once per account and Region. It creates the CDKToolkit stack; keep it for later lessons.
2Create the app and write the stack
Paste this whole block:
mkdir C:\aws-lab\saat-cdk | Out-Null
cd C:\aws-lab\saat-cdk
cdk init app --language typescript | Select-Object -Last 3
@'
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as ssm from 'aws-cdk-lib/aws-ssm';
export class SaatCdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const vpc = ec2.Vpc.fromLookup(this, 'SaatVpc', { vpcId: this.node.tryGetContext('vpcId') });
const logs = new s3.Bucket(this, 'Logs', {
encryption: s3.BucketEncryption.S3_MANAGED,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
enforceSSL: true,
lifecycleRules: [{ expiration: cdk.Duration.days(30) }],
});
const sg = new ec2.SecurityGroup(this, 'WebSg', { vpc, description: 'Web tier from CDK' });
sg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'HTTPS from anywhere');
const appRole = new iam.Role(this, 'AppRole', { assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com') });
logs.grantRead(appRole);
new ssm.StringParameter(this, 'LogBucketParam', { parameterName: '/saat/cdk/log-bucket', stringValue: logs.bucketName });
cdk.Tags.of(this).add('course', 'saatvedha-aws');
new cdk.CfnOutput(this, 'LogBucketName', { value: logs.bucketName });
}
}
'@ | Set-Content lib\saat-cdk-stack.ts
@'
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { SaatCdkStack } from '../lib/saat-cdk-stack';
const app = new cdk.App();
new SaatCdkStack(app, 'SaatCdkStack', {
env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: 'ap-south-1' },
});
'@ | Set-Content bin\saat-cdk.ts
cdk synth -c vpcId=$($v.VpcId) | Measure-Object -Line | Select-Object -ExpandProperty Lines
Get-Content cdk.context.json | Select-Object -First 3You should see something like this:
✅ All done!
...
212
{
"vpc-provider:account=111122223333:filter.vpc-id=vpc-0a1b2c3d4e5f67890:region=ap-south-1:returnAsymmetricSubnets=true": {
"vpcId": "vpc-0a1b2c3d4e5f67890",About 25 lines of TypeScript became a template of over 200 lines, including the bucket policy that enforces TLS and the IAM policy for grantRead. The VPC lookup result is cached in cdk.context.json; teams commit this file.
3Diff and deploy
Paste this, read the security changes it shows, then answer y:
cdk deploy -c vpcId=$($v.VpcId)You should see something like this (after the IAM and security group change tables):
Do you wish to deploy these changes (y/n)? y
SaatCdkStack: deploying... [1/1]
SaatCdkStack: creating CloudFormation changeset...
✅ SaatCdkStack
Outputs:
SaatCdkStack.LogBucketName = saatcdkstack-logs1a2b3c4d-9z8y7x6w5v4uThe approval prompt appears whenever IAM or security groups change. In a pipeline you’d review the same information in the pull request. You can open CloudFormation and see SaatCdkStack like any other stack.


4Change the code and see the diff
Raise retention to 60 days, diff, and deploy:
(Get-Content lib\saat-cdk-stack.ts -Raw) -replace 'Duration.days\(30\)', 'Duration.days(60)' | Set-Content lib\saat-cdk-stack.ts
cdk diff -c vpcId=$($v.VpcId)
cdk deploy -c vpcId=$($v.VpcId) --require-approval neverYou should see something like this:
Stack SaatCdkStack
Resources
[~] AWS::S3::Bucket Logs Logs6819BB44
└─ [~] LifecycleConfiguration
└─ [~] .Rules:
- "ExpirationInDays": 30
+ "ExpirationInDays": 60
✅ SaatCdkStackThe diff shows one property on one resource, no replacement. --require-approval never is fine here because no IAM changed; keep the prompt (the default) for anything touching permissions.
✅ Checkpoint: CDK is installed, ap-south-1 is bootstrapped, SaatCdkStack deployed with a bucket, security group, role and parameter, and a diff showed the 30 to 60 day change before you deployed it. Keep the stack for the break lab.
Break It, Then Fix It
Two CDK surprises: one stops you at synth, the other leaves something behind after destroy.
1Break it: look up a VPC that doesn’t exist
Synth with a made-up VPC ID:
cdk synth -c vpcId=vpc-0123456789abcdef0You’ll get an error like this:
[Error at /SaatCdkStack] Could not find any VPCs matching {"account":"111122223333","region":"ap-south-1","filter":{"vpc-id":"vpc-0123456789abcdef0"},"returnAsymmetricSubnets":true,"lookupRoleArn":"arn:aws:iam::111122223333:role/cdk-hnb659fds-lookup-role-111122223333-ap-south-1"}Lookups run against your real account during synth, so a wrong ID, the wrong Region or the wrong account’s credentials all end here. The opposite problem is quieter: once a lookup succeeds, its result is cached, so if the VPC later changes, synth keeps using the old answer. Fix: pass the right ID, and clear stale lookups when infrastructure changes:
cdk context --clear
cdk synth -c vpcId=$($v.VpcId) | Out-Null
"synth ok"You should see this:
synth ok2Break it: destroy the stack and check for leftovers
Destroy the stack, then look for the bucket:
$bucketName = aws cloudformation describe-stacks --stack-name SaatCdkStack --query "Stacks[0].Outputs[?OutputKey=='LogBucketName'].OutputValue" --output text
cdk destroy -c vpcId=$($v.VpcId) --force
aws s3api head-bucket --bucket $bucketName
"exit code: $LASTEXITCODE"You should see something like this:
SaatCdkStack: destroying... [1/1]
✅ SaatCdkStack: destroyed
{
"BucketRegion": "ap-south-1",
"AccessPointAlias": false
}
exit code: 0
The stack is gone but the bucket isn’t. CDK sets RETAIN on buckets by default, so data isn’t lost by accident. That’s right for production, but in dev accounts it leaves hundreds of orphaned buckets nobody owns.
Fix: delete leftovers, and for non-production stacks say what you want explicitly, for example removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true:
aws s3 rb "s3://$bucketName" --force
cd C:\aws-lab
$env:AWS_PROFILE = 'saat-admin'
Remove-Item Env:AWS_ACCESS_KEY_ID, Env:AWS_SECRET_ACCESS_KEY, Env:AWS_SESSION_TOKEN -ErrorAction SilentlyContinue
.\Find-LabResources.ps1Keep the C:\aws-lab\saat-cdk folder if you want to experiment further, and keep the CDKToolkit stack.
✅ Checkpoint: You fixed a failed VPC lookup and cleared the context cache, and found and removed a bucket that cdk destroy retained. SaatCdkStack is gone; the CDKToolkit bootstrap stack stays.
This happened at work
The 1,900 buckets nobody owned
A software company let every feature branch deploy its own CDK stack to a shared dev account, and destroyed it when the branch was merged. Two years later, a cost review found 1,900 S3 buckets and 600 log groups in that account, costing more each month than production storage.
Every bucket had been retained by default when its stack was destroyed. Some held test data copied from production, including customer names, which turned a cost problem into a data protection problem.
They added a CDK Aspect that sets DESTROY with autoDeleteObjects on all buckets in dev stacks and fails synth if a production stack doesn’t set an explicit removal policy, plus a weekly report of buckets without a live owning stack.
Common mistakes and troubleshooting
| Mistake | What happens | What to do |
|---|---|---|
| Assuming cdk destroy removes everything | Buckets and log groups are retained | Set removal policies explicitly; check for leftovers |
| Not committing cdk.context.json | Different results on different machines | Commit it; clear it deliberately when infra changes |
| Skipping cdk diff | Surprise replacements or IAM changes | Diff every change; keep the approval prompt for IAM |
| Deploying to an unbootstrapped Region | Error asking you to run cdk bootstrap | Bootstrap each account and Region once |
| Writing IAM policies by hand in CDK | Over-broad permissions | Use grant methods like grantRead |
| Long-lived access keys for the CDK | Leaked keys | Use temporary credentials (aws login, SSO or pipeline roles) |
Interview questions
Click a question to open it. Each one is written the way an interviewer would ask it. Under it you will see why they ask it and one way you could answer, in your own words.
Fresher level
1. Can you explain in simple words what the AWS CDK is?
Why they ask this: They want to know you understand how CDK relates to CloudFormation. Many people think it replaces CloudFormation, when it actually uses it.
How you might answer: The CDK lets you define AWS infrastructure in a programming language, like TypeScript or Python. When you run it, it generates a CloudFormation template and deploys it through CloudFormation.
So I still get stacks, change sets and rollback, but I write code instead of long YAML.
2. You try your first cdk deploy in a new account and it fails. Someone tells you to run cdk bootstrap. What does that actually do?
Why they ask this: They are checking that you understand the setup CDK needs in each account. It’s the first thing that trips people up in a new account or Region.
How you might answer: cdk bootstrap deploys the CDKToolkit stack to that account and Region. It creates an S3 bucket and an ECR repository for assets, and the IAM roles that CDK uses to deploy.
I have to run it once per account and Region before the first deploy.
3. What’s the difference between cdk synth and cdk deploy?
Why they ask this: They want to know you understand the CDK workflow. It shows whether you can check your work before touching a real account.
How you might answer: cdk synth only generates the CloudFormation template on my laptop, and nothing changes in AWS. cdk deploy synthesizes too, but then uploads assets and creates and executes a change set in my account.
I run synth first when I want to read the template before deploying.
2–5 years experience
4. How would you explain L1, L2 and L3 constructs to a new team member?
Why they ask this: They want to see that you understand the building blocks of CDK. Picking the right level affects how much code you write and how safe the defaults are.
How you might answer: I’d say an L1 construct maps directly to one CloudFormation resource, with no extras. An L2 adds sensible defaults and helper methods, like grants for permissions.
An L3 is a pattern that combines several resources into a common architecture, so you get a whole setup in a few lines.
5. You ran cdk destroy on a test stack, but the S3 bucket is still there. Why did that happen?
Why they ask this: They are checking that you know CDK protects data by default. They want to see you understand when it’s safe to change that.
How you might answer: Stateful resources like buckets default to RETAIN, so CDK leaves them behind to protect the data. For a dev stack where deleting is safe, I’d set removalPolicy to DESTROY, and autoDeleteObjects for buckets so it can empty them first.
6. A teammate opens a pull request that changes a CDK app. How would you review it safely?
Why they ask this: They want to see good change control. A small code change in CDK can create a big infrastructure change.
How you might answer: I’d run cdk diff in the pull request to see what will change in the account. I’d look at the synthesized template, and pay special attention to the security diff for IAM and security group changes.
Then the change would deploy through a pipeline, with an approval step before production.
Senior level
7. Your team is choosing between CDK, plain CloudFormation and Terraform. How would you help them decide?
Why they ask this: They want to see balanced judgement about tools. They are checking that you pick based on the team and the work, not on personal taste.
How you might answer: I’d say CDK suits teams that are comfortable with code and want abstractions, grants and testing on AWS. Plain CloudFormation is fine for small, stable templates, and Terraform makes sense for multi-cloud or teams already standardized on it.
What matters most is that we pick one tool, review every change, and stop changes in the console.
8. You have 50 CDK apps owned by different teams. How would you make sure they all follow the same security standards?
Why they ask this: They want to see governance at scale. They are checking that you build standards into the tools so teams don’t have to remember them.
How you might answer: I’d publish shared construct libraries with secure defaults, so teams get encryption and tags without extra work. I’d use Aspects to check or fix every construct for things like encryption, removal policies and tags.
In CI I’d run cdk-nag rules, and every pipeline would have diff review and approvals.
Quick quiz
Q1. What does the CDK produce before deploying?
- A shell script
- A CloudFormation template
- A Terraform plan
- An AMI
Show answer
B — cdk synth writes CloudFormation templates.
Q2. What must exist in an account and Region before the first cdk deploy?
- A VPC
- The bootstrap stack (CDKToolkit)
- An EC2 instance
- A KMS key
Show answer
B — cdk bootstrap creates it.
Q3. What does bucket.grantRead(role) do?
- Makes the bucket public
- Adds a least-privilege read policy for the role
- Deletes the bucket
- Enables versioning
Show answer
B — Grants write the IAM policy for you.
Q4. Why did the bucket survive cdk destroy?
- A bug
- Buckets default to RemovalPolicy RETAIN
- The stack failed
- Versioning was on
Show answer
B — Stateful resources are retained by default.
Q5. Where are lookup results cached?
- cdk.out
- cdk.context.json
- package.json
- S3
Show answer
B — Commit it, and clear it when infrastructure changes.
Quick recap
- The CDK turns code into CloudFormation; change sets, rollback and drift still apply.
- Bootstrap once per account and Region.
- L2 constructs give secure defaults and grant methods for least privilege.
- Run cdk diff before every deploy; read the security changes.
- Lookups are cached in cdk.context.json; stateful resources default to RETAIN.
Next lesson
Lesson 36: Cost management: Budgets, Cost Explorer, Savings Plans → You’ll set budgets with alerts, find what drives your bill in Cost Explorer and learn how Savings Plans and rightsizing cut costs.
Further reading: AWS CDK tutorial
Want to go deeper into AWS CDK tutorial? Read What is the AWS CDK? and AWS CDK bootstrapping. You can also see the full plan for this course on the AWS Cloud Administration course page.
