Home›DevOps›AWS CDK: Infrastructure as Code with Programming Languages
Advanced
4 min · July 12, 2026
AWS CDK: Infrastructure as Code with Programming Languages
A comprehensive guide to AWS CDK: Infrastructure as Code with Programming Languages on AWS, covering core concepts, configuration, best practices, and real-world use cases..
N
NarenFounder & Principal Engineer
20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.
✓Basic understanding of AWS services and cloud computing concepts.
✦ Definition~90s read
What is AWS CDK?
AWS CDK (Cloud Development Kit) lets you define AWS infrastructure using familiar programming languages like TypeScript, Python, or Java, compiling down to CloudFormation templates. It matters because it replaces YAML/JSON templating with real code constructs—loops, conditionals, and object-oriented patterns—enabling reusable, testable infrastructure.
★
AWS CDK: Infrastructure as Code with Programming Languages is like having a smart assistant that handles the heavy lifting so you don't have to manage servers yourself.
Use CDK when you need to manage complex, multi-service architectures that benefit from abstraction and shared logic, or when your team already lives in code and wants to avoid manual template maintenance.
Plain-English First
AWS CDK: Infrastructure as Code with Programming Languages is like having a smart assistant that handles the heavy lifting so you don't have to manage servers yourself.
⚙ Browser compatibility
Latest versions — ✓ supported
Chrome
Firefox
Safari
Edge
✓
✓
✓
✓
You've been burned by a 10,000-line CloudFormation template that no one dares to touch. A single typo in YAML took down production for 45 minutes. You're not alone—teams everywhere are ditching brittle templates for AWS CDK. CDK lets you define infrastructure in TypeScript, Python, Java, or C#, compiling down to CloudFormation. But it's not just syntax sugar: it's a paradigm shift. With constructs, you encapsulate best practices into reusable components. With unit tests, you validate infrastructure logic before deployment. And with the ability to use loops and conditionals, you eliminate copy-paste errors. This isn't about avoiding YAML—it's about treating infrastructure as real software. In this article, we'll cover CDK's core concepts, common pitfalls (like stateful resource deletion), and production patterns that keep your stacks safe.
Why AWS CDK Over Plain CloudFormation
AWS CloudFormation is powerful but verbose. A typical VPC with subnets, route tables, and NAT gateways can exceed 500 lines of YAML. The AWS Cloud Development Kit (CDK) replaces that with ~50 lines of TypeScript, Python, or Java. More importantly, CDK brings software engineering practices to infrastructure: loops, conditionals, functions, and unit tests. You can define reusable constructs, enforce policies with custom rules, and version your infrastructure alongside your application code. The trade-off is that CDK synthesizes to CloudFormation, so you inherit its deployment model and limits. But for teams that already write code, CDK reduces boilerplate and improves maintainability. In production, we've seen CDK cut infrastructure provisioning time by 60% and eliminate copy-paste errors across environments.
CDK is AWS-native and generates CloudFormation. Terraform is cloud-agnostic but requires state management. Choose CDK if you're all-in on AWS and want tight integration with IAM, Lambda, and other services.
📊 Production Insight
We once had a 2000-line CloudFormation template with a typo in a subnet CIDR. CDK's type system caught it at compile time. That alone saved us a failed deployment.
🎯 Key Takeaway
CDK replaces verbose YAML with familiar programming constructs, reducing boilerplate and enabling reuse.
thecodeforge.io
Aws Cdk Infrastructure
Core Concepts: App, Stack, and Construct
Every CDK project starts with an App, which contains one or more Stacks. A Stack maps to a CloudFormation stack and is the unit of deployment. Constructs are the building blocks: they can be low-level (like CfnBucket) or high-level (like Bucket with auto-configuration). The L2 constructs (e.g., s3.Bucket) provide sensible defaults and helper methods. L3 constructs are patterns like ecs.Patterns.ApplicationLoadBalancedFargateService. You compose constructs to build your infrastructure. In production, we always use L2 constructs unless we need raw CloudFormation resources. They handle permissions, dependencies, and resource names automatically. Never use L1 constructs unless you're extending an unsupported service.
Use consistent naming like <Project><Component>Stack. Avoid generic names like 'MyStack' — they cause confusion in multi-team accounts.
📊 Production Insight
We once had a team use L1 constructs for an S3 bucket and forgot to set blockPublicAccess. The bucket was publicly accessible for 3 days before a security scan caught it. L2 constructs set that by default.
🎯 Key Takeaway
App, Stack, and Construct form the CDK hierarchy. Use L2 constructs for production to get best practices out of the box.
Setting Up a CDK Project for Production
Initialize a CDK project with cdk init app --language typescript. This creates a standard structure with bin/, lib/, and test/ directories. For production, add cdk-nag for compliance checks, eslint for code quality, and jest for unit tests. Configure cdk.json with context variables for environment-specific settings (e.g., dev, prod). Use cdk.context.json for runtime context like AZs. Always pin the CDK version in package.json to avoid breaking changes. In CI/CD, run cdk synth and cdk deploy with --require-approval never only after manual approval. We use cdk watch for development but never in production pipelines.
CDK v2 is stable but breaking changes happen. Pin exact versions in package.json and use dependabot to review upgrades.
📊 Production Insight
We didn't pin CDK version once and a minor upgrade changed the default removal policy for S3 buckets from 'retain' to 'destroy'. We lost a week of logs.
🎯 Key Takeaway
Initialize with linting, testing, and compliance checks from day one. Pin CDK versions to avoid surprises.
thecodeforge.io
Aws Cdk Infrastructure
Writing Reusable Constructs
Reusable constructs are the heart of CDK. Create a construct by extending cdk.Construct and accepting scope, id, and props. Expose properties like vpc or bucket for other constructs to consume. For example, a SecureBucket construct that enforces encryption, versioning, and access logging. Use Grant methods to manage permissions instead of raw IAM policies. In production, we maintain a library of internal constructs shared across teams via a private npm registry. This ensures consistency: every S3 bucket has the same security posture. Avoid over-parameterization — expose only what's necessary. Too many props make constructs hard to use.
Name constructs after their purpose, not implementation. 'SecureBucket' is better than 'KmsEncryptedS3Bucket'.
📊 Production Insight
We built a 'SecureBucket' construct that automatically adds a lifecycle policy to expire incomplete multipart uploads after 7 days. This saved us from a $10k bill due to abandoned uploads.
🎯 Key Takeaway
Reusable constructs enforce best practices and reduce duplication. Keep props minimal and expose key resources.
Managing Environments with Context and Stages
Use CDK context to pass environment-specific values like account IDs, domain names, or feature flags. Define context in cdk.json or pass via -c key=value during synthesis. For multi-environment deployments (dev, staging, prod), use a Stage construct that groups stacks. Each stage can have different context. Use cdk deploy --context env=prod to select the environment. In production, we use a PipelineStack that deploys stages sequentially with manual approval gates. Never hardcode environment values in stack code — always use context or SSM Parameter Store.
Use context for non-sensitive values like instance types. Use SSM Parameter Store for secrets like database passwords.
📊 Production Insight
We had a production outage because a developer hardcoded a dev database URL in a stack. Context variables would have prevented that. Now we use SSM for all connection strings.
🎯 Key Takeaway
Use context and stages to manage multiple environments. Never hardcode environment-specific values.
Testing CDK Infrastructure
CDK supports three testing levels: unit tests with @aws-cdk/assertions, snapshot tests, and integration tests. Unit tests verify that constructs create the expected CloudFormation resources. Snapshot tests catch unintended changes. Integration tests deploy real resources and validate behavior. In production, we write unit tests for every custom construct. Use Template.fromStack(stack) to get the synthesized template and assert on resource counts, properties, and policies. Snapshot tests are useful but brittle — use them sparingly. Integration tests run in a separate AWS account and are triggered by pull requests. Never skip tests for infrastructure changes.
Snapshot tests are easy but fail on every minor change. Use them only for stable, rarely modified constructs.
📊 Production Insight
A unit test caught that our 'SecureBucket' construct didn't enforce SSL. We fixed it before deployment. Without tests, that bucket would have been vulnerable for months.
🎯 Key Takeaway
Unit test every construct. Use snapshot tests sparingly. Integration tests are mandatory for critical paths.
Deploying with CI/CD Pipelines
CDK Pipelines (formerly CDK Pipelines) is the recommended way to deploy CDK apps. It creates a self-mutating pipeline: the pipeline definition is itself CDK code. When you update the pipeline stack, it updates itself. Use CodePipeline or CodeBuild for the synth action. For production, add a ShellStep that runs cdk synth and cdk deploy. Use --require-approval never only after manual approval gates. Store pipeline configuration in context. Never use cdk deploy from a developer's machine for production — always use the pipeline. We've seen too many 'works on my machine' failures.
CDK Pipelines can update themselves. When you change the pipeline code, the pipeline deploys the change before deploying your app.
📊 Production Insight
We had a developer run cdk deploy with local changes that weren't committed. The stack drifted from the repo. Now we enforce that only the pipeline can deploy.
🎯 Key Takeaway
Use CDK Pipelines for production deployments. Never deploy manually. The pipeline should be the only way to change infrastructure.
Handling Secrets and Sensitive Data
Never hardcode secrets in CDK code. Use AWS Secrets Manager or SSM Parameter Store. CDK has built-in support for referencing secrets: Secret.fromSecretNameV2 or StringParameter.fromStringParameterName. For database passwords, use rds.DatabaseSecret which auto-generates and rotates passwords. In production, we use cdk-secrets-manager construct to create secrets with rotation. Always grant least-privilege access to secrets. Use secret.grantRead(lambda) instead of wildcard IAM policies. Rotate secrets regularly. We've seen teams embed API keys in context files committed to git — that's a breach waiting to happen.
Enable automatic rotation for database secrets. Use secret.addRotationSchedule with a Lambda rotation function.
📊 Production Insight
A team hardcoded an API key in a CDK context file and committed it to a public repo. Within hours, attackers used it to spin up expensive GPU instances. Always use Secrets Manager.
🎯 Key Takeaway
Use Secrets Manager for all secrets. Never hardcode or commit secrets. Grant least-privilege access.
Cost Management and Tagging
Tagging is essential for cost allocation. Use cdk.Tags.of(scope).add('key', 'value') to apply tags to all resources in a stack. For production, enforce tagging with cdk-nag rules. Use AwsSolutions-Tagging rule to require specific tags like Environment, CostCenter, and Owner. Also set removal policies: RemovalPolicy.DESTROY for non-production, RemovalPolicy.RETAIN for production data. Use cdk destroy for ephemeral environments. We've seen teams forget to tag resources and then struggle to identify cost spikes. Automate tagging with a custom aspect that applies tags to all constructs.
Use a standard set of tags: Environment, Application, CostCenter, Owner. Apply them at the app level with an aspect.
📊 Production Insight
Without tags, we couldn't identify which team's resources caused a $5k monthly bill. After enforcing tags, we found a forgotten dev environment running 24/7. Saved $3k/month.
🎯 Key Takeaway
Tag all resources for cost tracking. Use removal policies to protect production data. Automate tagging with aspects.
Advanced Patterns: Custom Resources and Escape Hatches
Sometimes CDK doesn't support a feature. Use escape hatches: (resource.node.defaultChild as CfnBucket).addPropertyOverride('VersioningConfiguration.Status', 'Suspended'). For custom logic, use CustomResource backed by a Lambda function. This is useful for tasks like DNS validation or database seeding. In production, we use custom resources to register SSL certificates with DNS validation. Be careful: custom resources are hard to debug. Always set a timeout and handle rollbacks. Use Provider framework for idempotency. We've seen custom resources fail silently and leave infrastructure in an inconsistent state.
Always implement idempotent handlers. Use the cr.Provider framework. Test rollback scenarios thoroughly.
📊 Production Insight
A custom resource for DNS validation failed during stack deletion, leaving a dangling Route53 record. We had to manually clean it up. Now we always implement Delete with a fallback.
🎯 Key Takeaway
Use escape hatches for unsupported properties. Use custom resources for complex orchestration, but handle failures carefully.
Monitoring and Observability
CDK can set up monitoring as part of your infrastructure. Use aws-cloudwatch constructs to create dashboards, alarms, and logs. For example, create an alarm when a Lambda function errors exceed a threshold. Use aws-ec2 flow logs for VPC traffic. In production, we use cdk-dashboards to create a unified dashboard per stack. Set up AWS::Lambda::Function with ReservedConcurrentExecutions to limit concurrency. Use aws-s3 server access logs. Never skip monitoring — it's the only way to detect issues before customers do. We've seen teams deploy without alarms and only discover outages from support tickets.
Create one dashboard per application. Include key metrics: error rate, latency, and throttles. Set alarms for pager-worthy events.
📊 Production Insight
We had a Lambda function that silently failed due to a missing environment variable. Without an error alarm, we didn't notice for 24 hours. Now every function has an alarm on errors.
🎯 Key Takeaway
Instrument monitoring as part of your CDK code. Set alarms for critical metrics. Create dashboards for operational visibility.
CDK vs CloudFormation: Key DifferencesComparing development experience and capabilitiesAWS CDKCloudFormationLanguageTypeScript, Python, Java, C#JSON or YAMLReusabilityConstructs as composable modulesNested stacks or macrosTestingUnit tests with assertionsManual validation onlyAbstractionHigh-level L2/L3 constructsLow-level resource definitionsState ManagementAutomatic via CDK toolkitManual stack managementTHECODEFORGE.IO
thecodeforge.io
Aws Cdk Infrastructure
Migration from CloudFormation to CDK
Migrating existing CloudFormation stacks to CDK requires care. Use cdk import to bring existing resources under CDK management. First, write a CDK stack that matches the current template exactly. Then run cdk import to sync state. After import, you can refactor. For large migrations, use cdk diff to compare. In production, we migrated a 50-stack environment by importing one stack at a time. Never delete and recreate resources — use import. We've seen teams try to recreate stacks and accidentally delete production databases. Always test migration in a non-production account first.
migrate.shBASH
1
2
3
4
5
6
7
# 1. WriteCDK stack that matches existing CloudFormation
# 2. Run cdk import
cdk import --stack-name ExistingStack
# 3. Verify no diff
cdk diff --stack-name ExistingStack
# 4. Now you can refactor the CDK code and deploy changes
cdk deploy --stack-name ExistingStack
Output
Stack imported successfully. No diff.
⚠ Import Limitations
Not all resources support import. Check AWS documentation. For unsupported resources, you may need to recreate them with a migration plan.
📊 Production Insight
We attempted to migrate a production RDS instance by recreating it. The new instance had a different endpoint, breaking all applications. Use import to preserve resource identifiers.
🎯 Key Takeaway
Use cdk import to adopt existing stacks. Never delete and recreate. Test migration in a non-production account.
⚙ Quick Reference
12 commands from this guide
File
Command / Code
Purpose
vpc-stack.ts
export class VpcStack extends cdk.Stack {
Why AWS CDK Over Plain CloudFormation
app.ts
const app = new cdk.App();
Core Concepts
setup.sh
mkdir my-cdk-app && cd my-cdk-app
Setting Up a CDK Project for Production
secure-bucket.ts
export interface SecureBucketProps {
Writing Reusable Constructs
pipeline-stack.ts
export class PipelineStack extends cdk.Stack {
Managing Environments with Context and Stages
secure-bucket.test.ts
test('SecureBucket enforces encryption', () => {
Testing CDK Infrastructure
pipeline.ts
const app = new cdk.App();
Deploying with CI/CD Pipelines
secret-stack.ts
export class SecretStack extends cdk.Stack {
Handling Secrets and Sensitive Data
tagging-aspect.ts
export class TaggingAspect implements cdk.IAspect {
Cost Management and Tagging
custom-resource.ts
export class DnsValidationRecord extends cdk.Construct {
Advanced Patterns
monitoring-stack.ts
export class MonitoringStack extends cdk.Stack {
Monitoring and Observability
migrate.sh
cdk import --stack-name ExistingStack
Migration from CloudFormation to CDK
Key takeaways
1
CDK is code, not config
Treat infrastructure as software: use loops, conditionals, and constructs to eliminate duplication and enforce best practices.
2
Stateful resources need protection
Always set removalPolicy: RemovalPolicy.RETAIN on databases and S3 buckets to prevent accidental deletion during cdk destroy.
3
Test your infrastructure
Use CDK's assertion library to validate synthesized templates. Catch IAM policy mistakes and resource misconfigurations before deployment.
4
Upgrade with caution
CDK versions can introduce breaking changes. Pin versions, review migration guides, and use cdk diff to inspect changes before deploying.
Follow AWS best practices and review documentation thoroughly
×
Ignoring cost implications
Symptom
Unexpected AWS bill at end of month
Fix
Set up billing alerts and use cost explorer to monitor usage
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01JUNIOR
What is AWS CDK: Infrastructure as Code with Programming Languages and w...
Q02SENIOR
How do you secure AWS CDK: Infrastructure as Code with Programming Langu...
Q03SENIOR
What are the cost optimization strategies for AWS CDK: Infrastructure as...
Q01 of 03JUNIOR
What is AWS CDK: Infrastructure as Code with Programming Languages and when would you use it?
ANSWER
AWS CDK: Infrastructure as Code with Programming Languages is an AWS service that helps manage cloud infrastructure efficiently. Use it when you need scalable, reliable cloud solutions.
Q02 of 03SENIOR
How do you secure AWS CDK: Infrastructure as Code with Programming Languages in production?
ANSWER
Follow the principle of least privilege, enable encryption at rest and in transit, and use AWS IAM roles with appropriate policies.
Q03 of 03SENIOR
What are the cost optimization strategies for AWS CDK: Infrastructure as Code with Programming Languages?
ANSWER
Use reserved instances for steady-state workloads, auto-scaling for variable demand, and right-size resources based on CloudWatch metrics.
01
What is AWS CDK: Infrastructure as Code with Programming Languages and when would you use it?
JUNIOR
02
How do you secure AWS CDK: Infrastructure as Code with Programming Languages in production?
SENIOR
03
What are the cost optimization strategies for AWS CDK: Infrastructure as Code with Programming Languages?
SENIOR
FAQ · 6 QUESTIONS
Frequently Asked Questions
01
What is the difference between CDK and CloudFormation?
CDK is a higher-level abstraction that compiles to CloudFormation templates. You write infrastructure in a programming language, which gives you loops, conditionals, and object-oriented reuse. CloudFormation is the underlying engine that provisions resources. CDK generates CloudFormation templates, so you still get all of CloudFormation's features (change sets, drift detection) but with less boilerplate.
Was this helpful?
02
How do I handle stateful resources like databases when destroying a stack?
By default, CDK's cdk destroy will delete all resources, including databases. To prevent accidental deletion, set removalPolicy: RemovalPolicy.RETAIN on stateful resources. For production, also enable deletionProtection on databases. Always test destruction in a non-production environment first.
Was this helpful?
03
Can I use CDK with existing CloudFormation templates?
Yes, via the CfnInclude construct. You can import an existing template and then extend it with CDK constructs. However, this is a migration path—you lose some CDK benefits like construct-level validation. Better to gradually refactor into pure CDK.
Was this helpful?
04
How do I manage secrets and environment-specific configurations?
Use AWS Secrets Manager or Parameter Store, and reference them via CDK's Secret or StringParameter constructs. For environment-specific values (e.g., stage names), use context variables (cdk.json or -c flag) or separate stacks per environment. Never hardcode secrets in code.
Was this helpful?
05
What are the common pitfalls when upgrading CDK versions?
Breaking changes in construct APIs, deprecated methods, and changes in default behavior (e.g., removal policies). Always review the migration guide. Use cdk diff to preview changes before deployment. Pin your CDK version in package.json and test upgrades in a dev environment first.
Was this helpful?
06
How do I test CDK applications?
Use the @aws-cdk/assertions library (or assertions in CDK v2) to write unit tests that verify synthesized CloudFormation templates. For integration tests, deploy to a sandbox account and run assertions against live resources. Always test IAM policies and resource configurations to catch security issues early.