Blogs/Technology

What is AWS CDK? A Complete Guide for Developers

Written byAkshay Bhimani
Aug 17, 2026
12 Min Read
What is AWS CDK? A Complete Guide for Developers Hero
Too Long? Read This First

- 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:

  1. Define: write constructs, stacks, stages, and their relationships.
  2. Synthesize: run cdk synth to generate the cloud assembly and CloudFormation templates.
  3. Test: assert important properties of the synthesized templates and validate organizational rules.
  4. Compare: run cdk diff to compare the proposed template with the deployed stack.
  5. Deploy: publish assets and submit the templates through CloudFormation.
  6. 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);
  • scope identifies the construct’s parent.
  • 'DocumentsBucket' is an ID unique within that scope.
  • props configures 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

ConceptResponsibility
ConstructA reusable component in the construct tree
StackThe smallest deployable unit; maps to one CloudFormation stack
AppThe root container that holds stacks or stages
StageA repeatable group of stacks, often representing dev, test, or production
EnvironmentThe AWS account and Region targeted by a stack
Construct
Responsibility
A reusable component in the construct tree
1 of 5

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 typescript

TypeScript 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-identity

Bootstrap the Target Environment

Before the first deployment, bootstrap each target account and Region:

npx cdk bootstrap aws://123456789012/ap-south-1

Bootstrapping 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-lambda

Create 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 synth

Inspect 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-only

cdk 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 broadening

For 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.

AreaAWS CDKRaw CloudFormationTerraform
AuthoringGeneral-purpose languageYAML or JSONHCL configuration
Provisioning engineCloudFormationCloudFormationTerraform providers
StateManaged through CloudFormation stacksManaged through CloudFormation stacksExplicit Terraform state and backend
Primary scopeAWS-focusedAWS-focusedCloud, on-premises, and SaaS providers
Reuse modelConstructs and normal language modulesNested stacks, modules, macros, transformsModules and provider ecosystem
Previewcdk diffChange setsterraform plan
Main strengthAWS-aware abstractions for software teamsDirect, transparent AWS resource definitionsConsistent workflow across providers
Main trade-offGenerated output and construct behavior must be understoodVerbose for reusable logicState security, locking, and provider lifecycle must be managed
Authoring
AWS CDK
General-purpose language
Raw CloudFormation
YAML or JSON
Terraform
HCL configuration
1 of 8

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.

Author-Akshay Bhimani
Akshay Bhimani

Full-stack engineer by day, bug whisperer by night. I code, debug, and conquer edge cases. Off the clock, I read tech threads, discussions, crashes, and enjoy Flipper Zero hacks.

Share this article

Phone

Next for you

8 Best GraphQL Libraries for Node.js in 2025 Cover

Technology

Aug 4, 202613 min read

8 Best GraphQL Libraries for Node.js in 2025

8 Best GraphQL Libraries for Node.js in 2026 Too Long? Read This First - Choose Apollo Server when you need a mature ecosystem, GraphOS integration, plugins, or Apollo Federation. - Choose GraphQL Yoga for a modern, portable server with Fetch API compatibility and built-in support for subscriptions over Server-Sent Events. - Choose Mercurius when your application already uses Fastify and runtime efficiency is a major priority. - Use GraphQL.js when you need the official JavaScript implementati

9 React Native Animation Libraries and Tools Compared Cover

Technology

Aug 4, 202615 min read

9 React Native Animation Libraries and Tools Compared

Too Long? Read This First - Use React Native Reanimated for gesture-driven, interruptible, and performance-sensitive interface animations. - Use the built-in Animated API for simple fades, transforms, and timed sequences without another dependency. - Pair React Native Gesture Handler with Reanimated for swipes, dragging, pinching, rotation, and other touch-driven experiences. - Use Lottie React Native for non-interactive motion graphics supplied by designers. - Choose React Native Skia for cust

9 Critical Practices for Secure Web Application Development Cover

Technology

Aug 4, 202616 min read

9 Critical Practices for Secure Web Application Development

Too Long? Read This First - Define security requirements and model threats before implementation begins. - Treat authentication, account recovery, and MFA as one complete identity system. - Apply server-side authorization to every protected action and object. - Prevent injection with parameterized APIs, structured validation, safe output handling, and restricted outbound requests. - Protect sessions and tokens throughout their complete lifecycle. - Minimise sensitive data and manage encryption