Imagine describing the cloud resources you need — a bucket for files, a database, a function — not by clicking checkboxes in the console and not in a long YAML file, but in ordinary code in a language you already know. You create a "bucket" object and you get a bucket. That is exactly what AWS CDK is: a way to describe infrastructure as a program.

CDK does not replace the usual tools; it builds on top of them. Under the hood CloudFormation still does the work — AWS's native deployment engine. CDK simply lets you write on top of it in a real language instead of hand-crafting templates. If the words "infrastructure as code" still sound abstract, take a look at the fundamentals of infrastructure as code, then come back here for the specifics about CDK.

What CDK is

CDK (Cloud Development Kit) is a framework that lets you describe cloud infrastructure as code in a familiar programming language. You write a program, run it, and out come ready-made resources in AWS.

The key idea in a nutshell: CDK does not deploy anything directly. It takes your code, turns it into an ordinary CloudFormation template (this is called "synthesis"), and then hands that template to CloudFormation, which actually creates and updates the resources. In other words, CDK has no deployment engine of its own — the engine is CloudFormation. CDK is a convenient layer in front of it.

Why bother, if you can write CloudFormation by hand? Because code gives you what a static template cannot:

  • loops and conditions — create ten identical queues in a loop instead of copying a block ten times;
  • variables and functions — pull a repeating chunk out into a function;
  • autocompletion and type checking in the editor — a typo is visible right away, not at deploy time;
  • ready-made libraries — a whole set of services "out of the box" with sensible settings.

The main library is called aws-cdk-lib — this is the second version of CDK (v2), which gathers all AWS services into a single package. Alongside it comes the constructs package — the basic building material, covered below.

Constructs: L1, L2, L3

A construct is the basic building block of CDK. Any piece of infrastructure you describe is a construct: a bucket, a database, an entire network. Constructs nest inside one another, and large ones are assembled from small ones.

Constructs come in three levels by degree of "convenience". A good analogy is assembling furniture:

L1 — "individual boards and screws". These are constructs with the Cfn prefix in their name (for example CfnBucket). Each L1 mirrors a CloudFormation resource one to one: all the same fields, nothing simplified, no sensible defaults — you set everything yourself. This is the lowest level, and you drop down to it rarely, when a property you need is missing at the higher levels.

new s3.CfnBucket(this, 'RawBucket', {
  versioningConfiguration: { status: 'Enabled' },
});

L2 — "an assembled cabinet with instructions". These are "polished" constructs with convenient defaults and clear methods. The same bucket at the L2 level is simply s3.Bucket: it sets safe defaults on its own and hides the unnecessary details. Turning on versioning is a single parameter.

new s3.Bucket(this, 'AppBucket', {
  versioned: true,
});

L3 — "a fully finished room". These are patterns: compositions of several L2 constructs that solve a typical task in one go. A single L3 construct can spin up a container service together with a load balancer, a network, and access rights all at once — something that would take dozens of lines by hand.

The rule for a beginner is simple: by default, reach for L2. They cover most tasks. Drop down to L1 only for a rare setting, and use L3 when you recognize your exact task in it.

App and Stack

A CDK description has a strict hierarchy of three nesting levels — it is handy to keep them in mind as "folders".

  • App — the root of everything, the CDK application itself. This is the point from which synthesis starts. App does not deploy anything on its own — it is merely a container for stacks.
  • Stack — the stack. This is the unit of deployment: one Stack turns into exactly one CloudFormation stack. A single App can contain several Stacks — for example, the network separately, the database separately, the application separately.
  • Constructs — the building blocks (L1/L2/L3) live inside a Stack.

The result is a tree: App → Stack → constructs. You describe your own stack as a class that extends Stack, and create the resources in its constructor.

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';

export class AppStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    new s3.Bucket(this, 'AppBucket', { versioned: true });
  }
}

The entry point is the file where the App is created and the stacks are added to it:

import * as cdk from 'aws-cdk-lib';
import { AppStack } from './app-stack';

const app = new cdk.App();
new AppStack(app, 'AppStack');
app.synth();

Note the three arguments on every construct: scope (the parent — where it lives), id (the name within the parent), and the properties. From the "parent + id" pair CDK builds a unique path to each resource in the tree.

How CDK turns into CloudFormation

Here it helps to see the whole path from code to running resources step by step. The commands are run through the cdk CLI tool.

Step 0 — cdk bootstrap (once per account and region). Prepares the supporting resources that CDK needs to work: a bucket for artifacts, access rights. Done once.

cdk bootstrap

Step 1 — cdk synth (synthesis). Runs your program and generates a CloudFormation template from it. This is exactly that "compilation" of code into a template. The template can be opened and read with your own eyes — useful for understanding what will actually be created.

cdk synth

Step 2 — cdk diff (what will change). Compares what is described in the code with what is already deployed, and shows the difference: which resources will be added, changed, or deleted. The habit of looking at diff before deploying saves you from accidental edits.

cdk diff

Step 3 — cdk deploy (deployment). Hands the template to CloudFormation, and it creates or updates the resources. Deployment is still handled by CloudFormation — with all of its properties: rollback on error, state tracking, safe updates.

cdk deploy

You can remove everything a stack created with the cdk destroy command. Wiring these commands into an automated pipeline is a separate task — worth reading about in the principles of CI/CD pipelines.

Which languages you write in

An important point that is often misunderstood: CDK has no "main" language. It supports several languages, and they are all equal:

  • TypeScript (and JavaScript)
  • Python
  • Java
  • Go
  • C#

These are not "wrappers of varying quality". Under the hood CDK uses a technology that translates the same library into all of these languages automatically, so the set of constructs, their properties, and their behavior are identical everywhere. Choose the language your team already writes in — that way the infrastructure lives next to the rest of the code, in one repository and with the same habits.

The examples in this article are in TypeScript simply because it is the most common choice for documentation and learning, not because it is the "correct" one. The same bucket in Python looks the same in meaning:

from aws_cdk import Stack, aws_s3 as s3
from constructs import Construct

class AppStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)
        s3.Bucket(self, "AppBucket", versioned=True)

To create a new project in the language you want, use the cdk init command with the --language flag (typescript, python, java, go, csharp).

cdk init app --language=python

Where it is used

CDK is chosen where there is a lot of infrastructure and it changes often, and the team already lives in the world of code: the same editors, the same reviews of changes, the same repository. It is especially handy when the infrastructure repeats — several identical environments or a dozen similar services: a loop and a function eliminate the copying that is unavoidable in a static template.

Typical beginner mistakes:

  • Thinking that CDK deploys resources itself. No — it synthesizes a CloudFormation template, and CloudFormation deploys it. All the limitations and behavior are inherited from it.
  • Jumping straight to the L1 level. Cfn constructs are verbose and have no defaults. Start with L2 and drop lower only when you genuinely need to.
  • Deploying without cdk diff. Without a look at the difference it is easy to delete a resource you never meant to touch.
  • Forgetting about cdk bootstrap. The first deployment in a new account or region simply will not go through without it.
  • Changing CDK-created resources by hand in the console. Then the code and reality drift apart, and the next deploy behaves unpredictably. The source of truth is the code.

Where to go next. It helps to understand CloudFormation itself — since CDK builds templates for it, knowing the foundation removes the magic. Alongside it, worth looking at Terraform — a different approach to the same task, so you understand what the options are. The topic of infrastructure state and delivery is worth mastering once you reach team work: where the state is stored, how to roll out changes safely. And to understand which resources you are managing in the first place, keep the AWS fundamentals, networking, and IAM access rights close at hand — without an understanding of the services themselves, any tool for managing them remains a set of incantations.