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 init in 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

  1. All examples use Python and CDK v2.
  2. Steps are tested on WSL2/Ubuntu on Windows and native macOS terminal.