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
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
| Level | Description | Example | Use When |
|---|---|---|---|
| L1 | Direct CloudFormation resource (Cfn prefix) | s3.CfnBucket | You need a setting not exposed by L2 |
| L2 | High-level construct with defaults, helpers, and grant methods | s3.Bucket, lambda_.Function | Default - use for everything |
| L3 | Multi-resource pattern (also called "Solutions Constructs") | aws_solutions_constructs.LambdaToDynamoDB | When 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:SendMessage3. 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 diffbeforecdk deploy- always preview changes before applying themcdk 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