CDK PATTERNS THAT ACTUALLY WORK_
I have deployed over thirty CDK stacks in production. Some of them were good. Most of the early ones were not. The patterns that survived are not the ones in the AWS documentation.
> THE PROBLEM WITH CDK EXAMPLES
Every CDK tutorial puts everything in one stack. One file. One class. new Bucket(this, 'MyBucket') next to new LambdaFunction(this, 'MyFunction') next to new RdsInstance(this, 'MyDatabase').
This works for a demo. It does not work for a production system with multiple environments, multiple teams, and a deployment pipeline that needs to update one component without touching the others.
The examples teach you the syntax. They do not teach you the architecture.
> MODULAR STACKS
Split your stacks by concern. Networking is one stack. Compute is another. Data layer is another. Shared services — IAM roles, KMS keys, SSM parameters — live in a foundation stack that everything else depends on.
The rule: if two things have different deployment cadences, they belong in different stacks. Your VPC does not change when you deploy a Lambda function. Do not put them in the same stack.
CloudFormation has a 500-resource limit per stack. You will hit it faster than you think if you put everything together. Modular stacks also mean faster deploys — CDK only synthesizes and deploys what changed.
> CONFIG-DRIVEN CONSTRUCTS
Hardcoded values are the enemy. Account IDs, region names, instance types, retention periods — none of these belong in your construct code.
Build a config object. Pass it in. Every environment — dev, staging, prod — gets its own config file. The construct code does not know which environment it is running in. It only knows what it was given.
This pattern also makes your stacks testable. You can instantiate the same construct with different configs and assert different outputs. If you cannot do that, your construct is not a construct — it is a script with extra steps.
> ESCAPE HATCHES
CDK does not expose every CloudFormation property. When you hit that wall — and you will — use the escape hatch.
cfnResource.addOverride('Properties.SomeProperty', value) drops you directly into the CloudFormation layer. It is not elegant. It is necessary. The alternative is waiting for the CDK team to add the property, which could be months.
Use escape hatches sparingly. Document them. They are technical debt with a known address.
> TESTING
Test the CloudFormation output, not the CDK code.
assertions.Template.fromStack(stack) gives you the synthesized CloudFormation template. Assert that the resources you expect exist with the properties you expect. This catches regressions when CDK upgrades change default behavior — and they do, quietly, in minor versions.
Write at least one test per construct. It takes ten minutes and has saved me from three production incidents.
> ANTI-PATTERNS TO AVOID
MEGA-STACKS — Everything in one stack. You will hit the resource limit, deploys will take forever, and a failed update will roll back everything at once.
HARDCODED ACCOUNT IDS — Use Stack.of(this).account and Stack.of(this).region. Your pipeline will thank you when you add a new environment.
UNPINNED CDK VERSIONS — CDK minor versions break things. Pin your version in package.json. Update deliberately, not accidentally.
USING CDK FOR EVERYTHING — CDK is excellent for AWS-native infrastructure. If you are managing multi-cloud resources or importing existing Terraform state, CDK will fight you. Use the right tool.
The thirty stacks still running in production share one thing: they were boring to deploy. No surprises. No manual steps. No drama.