I picked CDK over CloudFormation and Terraform for AWS-native infrastructure work - the ability to write real Python against AWS resources, with type checking and reusable constructs, made the rest of the tooling feel like a step backwards. These are the patterns I use in practice, covering Python CDK with a consistent use case across all chapters so the trade-offs are concrete rather than hypothetical.
Why CDK over CloudFormation for AWS-native workloads?
- CloudFormation is YAML or JSON - no loops, no conditionals, no abstraction. Any shared pattern gets copy-pasted.
- CDK uses a real programming language - loops, functions, classes, and type checks all apply to infrastructure the same way they apply to application code.
- L2 constructs handle boilerplate.
bucket.grant_read(fn)generates the IAM policy, role attachment, and resource reference in one call. The CloudFormation equivalent is four resources wired together manually. - The compiler catches mistakes before CloudFormation ever sees the template.
- CDK still synthesises to CloudFormation under the hood - rollbacks, drift detection, and stack history are unchanged.
Why CDK over Terraform for AWS-native workloads?
- Terraform’s strength is multi-cloud. For AWS-only workloads, that comes with overhead that doesn’t pay off - a state backend, a provider version to pin, and HCL alongside the application code.
- CDK uses CloudFormation as the deployment engine - AWS manages state natively. No S3 bucket for state, no DynamoDB table for locking, no
terraform initin the pipeline. - New AWS services appear in CDK constructs faster.
- IAM is easier. Terraform requires writing policy JSON by hand and threading ARNs between resources. CDK’s
grant_*methods generate least-privilege policies from the resource graph. - Terraform is the right call when infrastructure spans multiple cloud providers, or when the team already has a mature Terraform codebase.
Use case
The use case across all chapters: an S3-triggered Lambda with per-environment configuration varying across dev, staging, and prod.
- S3 bucket as the trigger source
- Lambda function processing uploaded files
- Per-environment config: bucket name, log level, Lambda timeout
- IAM role with least-privilege access
It hits every pattern worth knowing: L2 constructs, environment-specific config, unit tests against synthesised templates, and a multi-environment pipeline with approval gates. The infrastructure is not the point; the CDK patterns are.
The series
Chapter 1 - Project Setup and Bootstrapping
Getting a CDK project off the ground - directory structure, virtual environments, bootstrapping an AWS account, and understanding the synth/deploy cycle.
Chapter 2 - Managing Configuration and Context
How environment-specific values flow into CDK stacks - four approaches covering local static config, local dynamic config, SSM Parameter Store, and Secrets Manager, with trade-offs and code examples for each.
Chapter 3 - Writing and Sharing Constructs
Building L3 constructs - extracting resources into reusable units, the keyword-only props pattern, and sharing constructs across stacks.
Chapter 4 - Testing CDK Stacks
Unit testing constructs and stacks with aws_cdk.assertions - fine-grained assertions, snapshot testing, and when each applies.
Chapter 5 - CI/CD Pipelines
Automating CDK deployments with GitHub Actions, Jenkins, and Azure DevOps - auth setup, multi-environment promotion, and approval gates.
Notes
- All examples use Python and CDK v2.
- Steps are tested on WSL2/Ubuntu on Windows and native macOS terminal.