Infrastructure as Code with CDK

The AWS Cloud Development Kit lets you define cloud infrastructure in Python, TypeScript, Java, Go, or C# rather than YAML or JSON templates, with type safety, IDE completion, and real abstractions.

Introduction

CDK compiles your Python code into a CloudFormation template, then deploys it. This gives you the full power of a real programming language (loops, functions, imports, tests) to describe infrastructure, plus the reliability of CloudFormation for rollback and drift detection. The ByteCode web portal you are using right now was deployed with CDK.

1. App, Stack, and Construct

CDK Hierarchy

AppStack AStack BS3 Bucket (L2)Lambda (L2)VPC (L2)containscontains

An App contains Stacks; Stacks contain Constructs (AWS resources)

App

The root of your CDK program. Defined in app.py. Can contain multiple stacks (e.g., one for frontend, one for backend).

Stack

Maps 1:1 to a CloudFormation stack. All resources in a stack are deployed together and share a lifecycle. Cross-stack references pass values between stacks.

Construct

A reusable cloud component. L1 = raw CloudFormation resource. L2 = high-level with sane defaults (most common). L3 = opinionated multi-resource pattern.

2. L1, L2, and L3 Constructs

LevelDescriptionExampleUse When
L1Direct CloudFormation resource (Cfn prefix)s3.CfnBucketYou need a setting not exposed by L2
L2High-level construct with defaults, helpers, and grant methodss3.Bucket, lambda_.FunctionDefault - use for everything
L3Multi-resource pattern (also called "Solutions Constructs")aws_solutions_constructs.LambdaToDynamoDBWhen the pattern matches exactly; evaluate first

L2 constructs expose grant methods that automatically create the correct IAM policy. Instead of writing a policy document:

bucket.grant_read(fn) # grants s3:GetObject, s3:ListBucket bucket.grant_read_write(fn) # adds s3:PutObject, s3:DeleteObject table.grant_read_write_data(fn) # DynamoDB: GetItem, PutItem, Query, etc. queue.grant_send_messages(fn) # SQS: sqs:SendMessage

3. Setting Up a CDK Project

# Install CDK CLI globally
npm install -g aws-cdk

# Bootstrap your AWS account (one-time per account/region)
cdk bootstrap aws://123456789012/us-east-1

# Create a new Python CDK project
mkdir my-stack && cd my-stack
cdk init app --language python

# Install Python dependencies
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

4. A Complete API Stack in Python

This stack deploys a DynamoDB table, a Lambda function, and an HTTP API Gateway, wired together with automatic IAM grants. Everything you've learned in lessons 7, 9, and 10 in one coherent deployment:

# A more complete API stack
from aws_cdk import (
    aws_apigatewayv2 as apigwv2,
    aws_apigatewayv2_integrations as integrations,
    aws_dynamodb as dynamodb,
    aws_lambda as lambda_,
)

class ApiStack(cdk.Stack):
    def __init__(self, scope, id, **kwargs):
        super().__init__(scope, id, **kwargs)

        # DynamoDB table
        table = dynamodb.Table(self, "Orders",
            partition_key=dynamodb.Attribute(
                name="userId", type=dynamodb.AttributeType.STRING),
            sort_key=dynamodb.Attribute(
                name="orderId", type=dynamodb.AttributeType.STRING),
            billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST,
            removal_policy=cdk.RemovalPolicy.DESTROY,
        )

        # Lambda handler
        fn = lambda_.Function(self, "OrdersHandler",
            runtime=lambda_.Runtime.PYTHON_3_12,
            handler="orders.handler",
            code=lambda_.Code.from_asset("lambda"),
            environment={"TABLE_NAME": table.table_name},
        )
        table.grant_read_write_data(fn)

        # HTTP API Gateway
        api = apigwv2.HttpApi(self, "Api",
            cors_preflight=apigwv2.CorsPreflightOptions(
                allow_origins=["*"],
                allow_methods=[apigwv2.CorsHttpMethod.ANY],
            ),
        )
        api.add_routes(
            path="/orders",
            methods=[apigwv2.HttpMethod.GET, apigwv2.HttpMethod.POST],
            integration=integrations.HttpLambdaIntegration("OrdersInt", fn),
        )

        cdk.CfnOutput(self, "ApiUrl", value=api.api_endpoint)

5. CDK CLI Commands

# Preview what will change (no deployment)
cdk diff

# Synthesize CloudFormation template (inspect what CDK generates)
cdk synth

# Deploy a specific stack
cdk deploy MyStack

# Deploy all stacks in the app
cdk deploy --all

# Destroy resources (dev/test only)
cdk destroy MyStack

# List all stacks in the app
cdk ls

# View CDK context (account, region, environment values)
cdk context

Workflow: run cdk diff before every cdk deploy to see exactly what will change. In CI/CD, run cdk synth to validate the template without deploying, then cdk deploy --require-approval never in the deploy step.

Key Takeaways

  • App - Stack - Construct is the hierarchy; App contains Stacks which contain Constructs (resources)
  • L2 constructs are the default; they have sensible security defaults and grant methods that generate correct IAM policies
  • Grant methods (grant_read, grant_read_write_data) automate least-privilege IAM without writing policy JSON
  • cdk diff before cdk deploy - always preview changes before applying them
  • cdk bootstrap - required once per account/region to create the CDK toolkit resources (S3 bucket, IAM roles)
  • CDK synthesizes CloudFormation - you get CloudFormation's reliability and rollback while writing Python