What is AWS CDK? A Complete Guide for Developers

- AWS CDK is an open-source infrastructure-as-code framework for AWS.
- We define resources with constructs in a supported programming language; CDK synthesizes them into CloudFormation.
- L1 constructs closely represent CloudFormation resources, L2 constructs provide intent-based AWS APIs, and L3 constructs combine multiple resources into reusable patterns.
- A stack is the smallest deployment unit. An app can contain multiple stacks, while a stage groups stacks for environments such as development and production.
-
cdk synth, tests, and cdk diff should run before deployment. Production changes should normally flow through CI/CD with an approval policy.- CDK removes no responsibility for IAM, data retention, observability, quotas, replacements, or cost. Abstraction makes infrastructure easier to express, not automatically safe.
- CDK itself has no additional charge, but the AWS resources and certain CloudFormation extensions it provisions may incur charges.
Infrastructure definitions often begin as a few manageable YAML files. Then environments multiply, permissions become conditional, resource names must be shared between services, and the template grows into something nobody wants to edit without a long review.
The AWS Cloud Development Kit, or AWS CDK, addresses that authoring problem. It lets us model AWS infrastructure in TypeScript, JavaScript, Python, Java, C#, or Go, using normal language features and reusable components. CDK then synthesizes that program into AWS CloudFormation templates and assets for deployment.
That last detail is important. CDK is not an alternative provisioning engine hidden behind TypeScript. CloudFormation still creates, updates, rolls back, and tracks the resources. CDK gives us a more expressive way to build the desired CloudFormation model.
What Is Infrastructure as Code?
Infrastructure as Code (IaC) means defining infrastructure in version-controlled files rather than configuring it manually in a console. A reviewed change can create or update networks, compute, databases, queues, alarms, identities, and policies in a repeatable way.
Good IaC provides more than automation:
- Reproducibility: development, staging, and production come from the same reviewed definition.
- Traceability: source control shows what changed, why it changed, and who approved it.
- Reviewability: security, reliability, and cost implications can be inspected before deployment.
- Recovery: an environment can be recreated or repaired from a known definition.
- Standardization: teams can package approved architecture patterns instead of rediscovering them in each service.
IaC does not make infrastructure immutable or eliminate operational risk. A declarative template can still grant excessive permissions, replace a database, expose a bucket, or create an expensive NAT Gateway. The quality of the model and the deployment controls still matter.
How AWS CDK Works
An AWS CDK application is executable code. When the CDK CLI runs it, the framework builds an in-memory tree of constructs and produces a cloud assembly, normally under cdk.out. That assembly contains CloudFormation templates, asset metadata, and deployment instructions.
The typical lifecycle is:
- Define: write constructs, stacks, stages, and their relationships.
- Synthesize: run
cdk synthto generate the cloud assembly and CloudFormation templates. - Test: assert important properties of the synthesized templates and validate organizational rules.
- Compare: run
cdk diffto compare the proposed template with the deployed stack. - Deploy: publish assets and submit the templates through CloudFormation.
- Observe: monitor CloudFormation events, application health, cost, and drift after deployment.
CDK code runs during synthesis, so it should be deterministic and free of side effects. Synthesis usually creates local output, but it can query an AWS account when a construct performs an environment lookup. CDK caches those lookup results in cdk.context.json; AWS recommends committing that file so local and CI synthesis do not silently produce different templates.
Some values are not known until deployment. For example, an automatically named S3 bucket has no final name at synthesis time. CDK represents these unresolved values as tokens and emits the appropriate CloudFormation reference. We can pass such a value to another construct, but we cannot reliably inspect it as a normal string while the CDK program is running.
The AWS CDK Building Blocks
Constructs
A construct is a component in the CDK construct tree. It can represent one CloudFormation resource, an AWS resource with helpful behavior, or an entire architecture pattern.
Every construct receives three core arguments:
new s3.Bucket(scope, 'DocumentsBucket', props);scopeidentifies the construct’s parent.'DocumentsBucket'is an ID unique within that scope.propsconfigures the construct.
The scope and ID contribute to the construct path and the generated CloudFormation logical ID. Renaming or moving a stateful construct can therefore look like resource replacement to CloudFormation. This is one of the most important CDK behaviors to understand before refactoring production infrastructure.
L1 Constructs: Direct CloudFormation Coverage
L1 constructs are generated from the CloudFormation resource specification. Their names begin with Cfn, such as s3.CfnBucket, and their properties closely follow the corresponding AWS::S3::Bucket schema.
const bucket = new s3.CfnBucket(this, 'DocumentsBucket', {
bucketEncryption: {
serverSideEncryptionConfiguration: [
{
serverSideEncryptionByDefault: {
sseAlgorithm: 'AES256',
},
},
],
},
});L1 constructs offer broad service coverage and precise control. They are useful when a new CloudFormation property is not yet exposed by an L2 construct, or when we need to map an existing template closely. The trade-off is verbosity and fewer helper methods.
L2 Constructs: Intent-Based AWS Resources
L2 constructs provide higher-level, service-aware APIs. They often add sensible defaults, validation, references to related resources, and methods such as grantRead or addEventNotification.
const bucket = new s3.Bucket(this, 'DocumentsBucket', {
versioned: true,
encryption: s3.BucketEncryption.S3_MANAGED,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
enforceSSL: true,
removalPolicy: cdk.RemovalPolicy.RETAIN,
});L2 is usually the best starting point, but “higher level” does not mean “production secure by default.” We still need to choose encryption, network exposure, authentication, logging, retention, backups, and removal policies deliberately. Defaults can also change as construct versions evolve, so important properties should be explicit and tested.
L3 Constructs: Reusable Architecture Patterns
L3 constructs, also called patterns, compose multiple resources into an opinionated capability. An ApplicationLoadBalancedFargateService, for example, can define a load balancer, listener, ECS service, task resources, networking relationships, and IAM roles through a compact API.
That convenience can create dozens of CloudFormation resources. Before adopting an L3 pattern, synthesize it and inspect its cost, network topology, permissions, scaling behavior, logging, and operational defaults. Fewer lines of CDK do not mean fewer deployed resources.
Our practical rule is:
- start with L2 for normal AWS resources;
- use L1 when we need a property or resource that L2 does not expose;
- create or adopt L3 patterns when a well-understood architecture should be repeated consistently.
Stacks, Apps, Stages, and Environments
| Concept | Responsibility |
| Construct | A reusable component in the construct tree |
| Stack | The smallest deployable unit; maps to one CloudFormation stack |
| App | The root container that holds stacks or stages |
| Stage | A repeatable group of stacks, often representing dev, test, or production |
| Environment | The AWS account and Region targeted by a stack |
Stack boundaries should follow deployment and ownership boundaries, not arbitrary folder structure. Stateful resources may deserve a separate stack with termination protection, while tightly coupled resources are usually easier to maintain together. Excessive cross-stack references make independent deployment and deletion harder.
AWS environments are account-and-Region pairs. An environment-specific stack can perform lookups and use regional facts during synthesis; an environment-agnostic stack is more portable but has less environment-specific information available.
Partner with Us for Success
Experience seamless collaboration and exceptional results.
Getting Started with AWS CDK v2
AWS CDK v2 is the supported major version. CDK v1 ended support in June 2023. Version 2 places stable AWS service constructs in a single aws-cdk-lib package; experimental modules remain separately versioned packages.
Prerequisites
We need:
- an AWS account;
- a supported Node.js version, because the CDK CLI runs on Node.js even when the app is written in Python, Java, C#, or Go;
- AWS CLI credentials, preferably through AWS IAM Identity Center for local development;
- permission to bootstrap and deploy into the target environment.
Install the CDK CLI and create a TypeScript project:
npm install --global aws-cdk
mkdir orders-infrastructure
cd orders-infrastructure
cdk init app --language typescriptTypeScript and JavaScript projects include a versioned local CLI dependency. Using npx cdk in scripts and CI helps keep the project’s tooling reproducible.
Confirm the AWS identity before changing an account:
aws sts get-caller-identityBootstrap the Target Environment
Before the first deployment, bootstrap each target account and Region:
npx cdk bootstrap aws://123456789012/ap-south-1Bootstrapping creates a CloudFormation stack named CDKToolkit by default. Its resources can include an S3 bucket for file assets, an ECR repository for container assets, and IAM roles used during deployment.
“Once per account and Region” is a useful starting rule, not a lifetime guarantee. Bootstrap templates evolve, cross-account pipelines may require specific trust settings, and security controls may require a customized bootstrap stack. Treat bootstrap configuration as privileged infrastructure and review its IAM scope.
Practical Example: S3, Lambda, and API Gateway
The following TypeScript example creates:
- a private, encrypted, versioned S3 bucket;
- a Node.js 24 Lambda function;
- least-privilege read access from Lambda to the bucket;
- an API Gateway REST endpoint protected with IAM authorization.
Install the runtime and bundling dependencies:
npm install @aws-sdk/client-s3
npm install --save-dev esbuild @types/aws-lambdaCreate lib/documents-api-stack.ts:
import * as path from 'node:path';
import * as cdk from 'aws-cdk-lib';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import {Construct} from 'constructs';
export class DocumentsApiStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const documents = new s3.Bucket(this, 'Documents', {
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
encryption: s3.BucketEncryption.S3_MANAGED,
enforceSSL: true,
versioned: true,
removalPolicy: cdk.RemovalPolicy.RETAIN,
});
const listDocuments = new lambdaNodejs.NodejsFunction(
this,
'ListDocuments',
{
entry: path.join(__dirname, '../lambda/list-documents.ts'),
handler: 'handler',
runtime: lambda.Runtime.NODEJS_24_X,
memorySize: 256,
timeout: cdk.Duration.seconds(10),
environment: {
DOCUMENTS_BUCKET: documents.bucketName,
},
},
);
documents.grantRead(listDocuments);
const api = new apigateway.RestApi(this, 'DocumentsApi', {
deployOptions: {
loggingLevel: apigateway.MethodLoggingLevel.ERROR,
metricsEnabled: true,
},
});
api.root.addResource('documents').addMethod(
'GET',
new apigateway.LambdaIntegration(listDocuments),
{authorizationType: apigateway.AuthorizationType.IAM},
);
}
}Create lambda/list-documents.ts:
import {ListObjectsV2Command, S3Client} from '@aws-sdk/client-s3';
import type {APIGatewayProxyHandler} from 'aws-lambda';
const s3 = new S3Client({});
export const handler: APIGatewayProxyHandler = async () => {
const bucket = process.env.DOCUMENTS_BUCKET;
if (!bucket) {
throw new Error('DOCUMENTS_BUCKET is not configured');
}
const result = await s3.send(
new ListObjectsV2Command({Bucket: bucket, MaxKeys: 20}),
);
return {
statusCode: 200,
headers: {'content-type': 'application/json'},
body: JSON.stringify({
documents: (result.Contents ?? []).map(({Key, Size}) => ({Key, Size})),
}),
};
};The grantRead call expresses intent and generates the required IAM policy. It is safer than grantReadWrite when the function only lists or reads objects. The bucket name in the environment variable is a deploy-time token; CloudFormation resolves it and creates the dependency between resources.
This is still a compact example, not a complete public API. IAM authorization requires callers to sign requests with AWS credentials. A consumer-facing application may instead use a suitable authorizer, throttling, request validation, WAF controls, structured logs, alarms, and a deliberate data-access model.
Synthesizing, Testing, Comparing, and Deploying
1. Synthesize the Template
npm run build
npx cdk synthInspect the generated template when adopting a construct or changing its version. Look for unexpected IAM actions, public access, resource counts, replacements, custom resources, and retention settings.
2. Test the Infrastructure Model
CDK assertions test the synthesized CloudFormation, not whether an AWS service behaves correctly at runtime. They are well suited to enforcing properties that must never regress.
import * as cdk from 'aws-cdk-lib';
import {Template} from 'aws-cdk-lib/assertions';
import {DocumentsApiStack} from '../lib/documents-api-stack';
test('the documents bucket is encrypted and blocks public access', () => {
const app = new cdk.App();
const stack = new DocumentsApiStack(app, 'TestStack');
const template = Template.fromStack(stack);
template.hasResourceProperties('AWS::S3::Bucket', {
BucketEncryption: {
ServerSideEncryptionConfiguration: [
{
ServerSideEncryptionByDefault: {
SSEAlgorithm: 'AES256',
},
},
],
},
PublicAccessBlockConfiguration: {
BlockPublicAcls: true,
BlockPublicPolicy: true,
IgnorePublicAcls: true,
RestrictPublicBuckets: true,
},
VersioningConfiguration: {Status: 'Enabled'},
});
});Fine-grained assertions are usually more useful than approving a large snapshot without reading it. Add integration or canary tests after deployment for permissions, networking, event delivery, and runtime behavior.
3. Review the Difference
npx cdk diff
npx cdk diff --security-onlycdk diff helps reveal additions, removals, policy changes, and replacements. It is a review aid, not a proof that the deployment is harmless. A small property change can still restart a service, replace a resource, or alter runtime behavior.
4. Deploy Through a Controlled Identity
npx cdk deploy --require-approval broadeningFor production, prefer a CI/CD role with limited, auditable permissions over individual administrator credentials. Keep approval policy in the pipeline, restrict bootstrap roles, and use permissions boundaries or organizational controls when teams must not create certain resources or permissions.
Avoid --hotswap for production. Hotswap can update supported resources directly instead of deploying the complete CloudFormation change set, and it disables rollback. It is useful for development iteration, not for preserving production infrastructure state.
Production Practices That Matter
Keep Synthesis Deterministic
Do not create resources, mutate accounts, or call arbitrary live APIs as a side effect of synthesis. Use CDK context providers for supported lookups, commit cdk.context.json, and refresh context intentionally. The same commit should synthesize the same template for the same environment.
Treat Construct IDs as Infrastructure Identity
CloudFormation logical IDs are derived partly from construct paths. Moving a database into a new construct or renaming an ID can cause replacement even when its properties are unchanged. Always review cdk diff, test logical IDs for critical resources, and use CDK refactoring features where appropriate.
Make Destructive Behavior Explicit
Set removal policies based on the data lifecycle. RETAIN protects stateful production resources from stack deletion but can leave chargeable orphaned resources. DESTROY may be appropriate for ephemeral development data, while snapshots suit only resource types that support them. Pair retention with backup and cleanup policies.
Grant Only the Permission the Workload Needs
Prefer focused methods such as grantRead over broader grants. Inspect synthesized IAM policies, watch wildcard resources and actions, and review security-broadening diffs. CDK can generate least-privilege relationships only when our requested intent is itself narrow.
Keep Secrets Out of Source, Context, and Outputs
Do not place secret values in construct properties, cdk.json, cdk.context.json, stack outputs, or Lambda environment variables as plaintext. Store secrets in AWS Secrets Manager or Systems Manager Parameter Store and grant the runtime identity permission to retrieve them.
Partner with Us for Success
Experience seamless collaboration and exceptional results.
Separate Environments by Account Where Practical
Development and production in separate AWS accounts create a stronger isolation boundary than naming conventions inside one account. Use stages to model repeated stack groups, but give each stage explicit account, Region, configuration, approval, and observability requirements.
Monitor Drift and Runtime Health
CloudFormation tracks stack resources, but manual console changes can create drift. Detect drift deliberately, restrict direct changes, and monitor the workloads after deployment. A successful CloudFormation event confirms provisioning, not application correctness.
AWS CDK vs CloudFormation vs Terraform
These tools overlap, but they differ in authoring model, execution, and operational state.
| Area | AWS CDK | Raw CloudFormation | Terraform |
| Authoring | General-purpose language | YAML or JSON | HCL configuration |
| Provisioning engine | CloudFormation | CloudFormation | Terraform providers |
| State | Managed through CloudFormation stacks | Managed through CloudFormation stacks | Explicit Terraform state and backend |
| Primary scope | AWS-focused | AWS-focused | Cloud, on-premises, and SaaS providers |
| Reuse model | Constructs and normal language modules | Nested stacks, modules, macros, transforms | Modules and provider ecosystem |
| Preview | cdk diff | Change sets | terraform plan |
| Main strength | AWS-aware abstractions for software teams | Direct, transparent AWS resource definitions | Consistent workflow across providers |
| Main trade-off | Generated output and construct behavior must be understood | Verbose for reusable logic | State security, locking, and provider lifecycle must be managed |
CDK vs CloudFormation
CDK builds CloudFormation, so both ultimately share CloudFormation’s deployment behavior, quotas, rollback semantics, and resource-replacement rules. CDK adds composition, type checking, loops, helper methods, and testable abstractions. Raw templates make the final resource model more direct and avoid executing a general-purpose program during synthesis.
Moving an existing estate to CDK can still be a migration. CfnInclude can wrap an existing template, cdk import can bring supported deployed resources under a stack, and cdk migrate can generate an initial L1-based app. None of these removes the need to verify logical IDs, drift, dependencies, and replacement behavior.
CDK vs Terraform
Terraform is designed to manage resources across multiple clouds and other providers through one workflow. It maintains explicit state that maps configuration addresses to remote objects, so teams must secure the state backend and use locking. CDK delegates AWS resource state to CloudFormation and offers deeper AWS-specific L2 and L3 abstractions.
Choose based on the operating model rather than syntax preference. An AWS-only team building reusable TypeScript constructs may move faster with CDK. An organization standardizing infrastructure across AWS, another cloud, Kubernetes, and SaaS providers may gain more from Terraform’s provider model.
When AWS CDK Is a Good Fit
CDK is a strong choice when:
- the infrastructure is primarily on AWS;
- the team is comfortable reviewing application code and generated templates;
- reusable, organization-approved AWS patterns will reduce repetition;
- infrastructure and application code benefit from shared types and tooling;
- CloudFormation is already an accepted provisioning engine;
- the delivery process can run synthesis, assertions, diffs, and controlled deployments.
CDK may be a poor fit when:
- one IaC workflow must manage several cloud and SaaS providers;
- the organization is already standardized successfully on another tool;
- operators prefer direct declarative templates and do not want a language runtime in synthesis;
- generated abstractions would make security or compliance review harder rather than easier;
- the team is not prepared to inspect CloudFormation behavior beneath the construct API.
The language should follow the team that will maintain the infrastructure. TypeScript gives the most direct CDK development experience because the framework is implemented in TypeScript, but all six documented languages are supported. Consistency and maintainability matter more than choosing the most fashionable option.
Frequently Asked Questions
Does AWS CDK replace CloudFormation?
No. CDK synthesizes constructs into CloudFormation templates, and CloudFormation provisions and tracks the resources. CDK changes the authoring experience while retaining CloudFormation deployment behavior, rollback semantics, quotas, and replacement rules.
Which programming languages does AWS CDK support?
AWS officially supports TypeScript, JavaScript, Python, Java, C#, and Go. Choose the language the infrastructure maintainers can review confidently; TypeScript offers the most direct ecosystem experience, but it is not mandatory.
Does AWS CDK cost anything?
AWS CDK has no additional charge. We still pay for provisioned AWS resources, data transfer, and applicable CloudFormation extensions or hooks. Destroy unused development stacks and review retained resources to control costs.
Must every AWS account and Region be bootstrapped?
Each target account-and-Region environment must be bootstrapped before stacks requiring CDK assets can deploy there. Bootstrap templates may later need upgrades, trust changes, permissions boundaries, or organizational security controls and customization.
Can AWS CDK manage existing resources?
Yes, through several approaches. We can reference unmanaged resources with static import methods, include CloudFormation templates with CfnInclude, or safely adopt supported resources using cdk import or cdk migrate workflows.
How should we store secrets in a CDK application?
Keep secret values out of source code, context files, templates, and outputs. Define or reference Secrets Manager or Parameter Store resources, then grant only the runtime identity permission to retrieve them.
Final Thoughts
AWS CDK is most valuable when it turns proven infrastructure decisions into reusable, reviewable components. Its real advantage is not that TypeScript is shorter than YAML. It is that a team can encode intent—private storage, narrow permissions, consistent alarms, approved networking—and test the resulting CloudFormation model before deployment.
The abstraction is useful only while we understand what it produces. We should inspect synthesized templates, protect logical identities, keep synthesis deterministic, review security changes, test deployed behavior, and operate the resulting resources like any other production system.
Used with that discipline, CDK gives AWS-focused engineering teams a productive infrastructure language without abandoning CloudFormation’s deployment engine.



