Why the AWS CDK won the war (inside my head)
Disclaimer: This is NOT one of those “Terraform bad” posts. I loved Terraform and wonder about its future, but by no means do I think Terraform has lost its shine and place in this world. This is an opinionated piece about the AWS CDK and me.
It naturally happens that the first thing someone asks about the AWS CDK is, “Why don’t you just use Terraform?” (and someone else may add, “Why don’t you use Pulumi?”). It is multi-cloud-compatible, and you will not be vendor-locked in. The vendor lock-in will happen no matter the tool you use and what you do, so don’t worry about that. I am assuming that if you are considering using the AWS CDK, it means you are already stuck for life with AWS.
And that’s ok, if used in the right way, AWS is immensely powerful. Yes, it is expensive as hell (on paper). Still, the surcharge easily translates into not having to maintain a 20-person infrastructure team that only keeps servers running and instead having a way smaller DevOps team that provides the rest of the organization with ready-to-use tools to improve their jobs and smoothen their lives. While someone else takes care in your stead of keeping the servers running and updating the software, all you have to use is an SDK to interact with all the components.
I want to talk about what I like and what are some common complaints I hear. As a premise, I use the TypeScript variant of the AWS CDK, which means my comments apply to this environment and no others.
Table of Contents
· Like: How easy it is to alter the source code
· Concern: Yes, but what if something is not supported by CloudFormation?
· Complaint: CloudFormation outputs suck
· Like A LOT: Automatic rollbacks
· Like A LOT: IAM helpers
· Like: Lambdas and events are at the core of the world
· Like, LOVE: Testing snapshots
· Conclusion
Like: How easy it is to alter the source code
My eternal reference: https://github.com/aws/aws-cdk/tree/main/packages/aws-cdk-lib/
I had this case some time ago where I needed to use the ForwardedValues
field in the CloudFront distribution construct. That field is considered deprecated and has been deprecated for 4 years, but some things are still used when your infrastructure is a teenager.
The official AWS CDK CloudFront package no longer supports it in the L2 construct (you can see it’s not available in the CacheBehavior
class properties), so how can I add it? Well, one look at the docs and you can notice the ForwardedValues
field is still available in the CfnDistribution
L1 construct properties 👀
So, the solution is pretty simple. Copy/paste just works. Yeah. Take part of the L2 construct’s code, copy/paste it, and add the field yourself to a new version of the CacheBehavior
class.
Just a look will allow me to alter anything related to the source code. Plus, we are talking about using a programming language to generate CloudFormation templates, which means I can use any language functionality and customize it to hell!
Concern: Yes, but what if something is not supported by CloudFormation?
Tools like Terraform have an edge here, based on API calls and not on CloudFormation templates. Let’s take the example of cross-account VPC peerings. Terraform offers a convenient aws_vpc_peering_connection_accepter
resource that can easily use multiple provider configurations to auto-accept peering requests.

This is clearly not doable using the constructs built in the AWS CDK natively.
BUT, we have something called CustomResource
, that lets us run a Lambda function, which in turn can make an API call. Once you can use a CustomResource
you can have your own custom peering accepter that, for example, is a Lambda function that invokes the "accept VPC peering connection" API call and sets up the route tables related to the connection.
There are ways around things that are not that complicated once you get the hang of the concept.
P.S. Are you working on the edge and want to use constructs that have not yet been integrated into the CDK and cannot wait for about a week? You can go on using the brutal CfnResource
construct 😈
Complaint: CloudFormation outputs suck
As I wrote in the AWS CDK: Cross-stack references post, CloudFormation outputs can present their challenges. If any stack A in your account imports a value exported by another stack B, the output created by stack B cannot be changed. To ensure services’ health, this causes you to have to follow a multi-step deployment pattern similar to:
- Create a new resource
- Repoint your services to the new resource
- Delete the old resource
When dealing with tools like Terraform, you have the readily-available create_before_destroy
parameter, which will first create a new resource and then delete the old one, giving you a smooth transition between the two states.
This is a case where you may say easily, “Terraform good, CDK bad,” but what I feel is that the two tools have two very distinct philosophies:
- Terraform operates on a multi-account, multi-provider level.
- The AWS CDK operates on CloudFormation stacks deployed in one account.
Terraform’s approach has an edge, of course, as it can orchestrate the deployment of new resources by making sure their references can be updated in all the various dependents, as long as they are included in the project.
The AWS CDK approach is instead focused on ensuring that you don’t break your infrastructure. If your stack A creates a bunch of subnets and exports their IDs, and another stack B uses these IDs to set up some routes, you don’t want stack A to go on and just try to delete the subnets, right? If your infrastructure is made of components that are independently interconnected, you need one method to force them to collaborate with each other, and that is to block changing the outputs.
But I cannot even ADD a subnet.
Yes, and you cannot even DELETE a subnet.
So, what is your way out once you are stuck with outputs? Change your mindset. Either you transition to using SSM parameters, or you structure your infrastructure differently and make use, for example, of lookups (where possible) and the various xxx.fromArn/fromXXXName
methods, or you accept that multi-step deployments are easy to do and don't hurt anyone, and it's perfectly ok to change your practices.
Because you know what happens when you do multi-stage deployment and something fails?
Like A LOT: Automatic rollbacks
This is on my top-tier list of things I love about the AWS CDK. Something is broken in your deployment, and you pushed out changes that don’t make sense to AWS, and errors and failures appear all over the place in your deployment logs.
Well, nothing broke. Why? CloudFormation will just roll back your changes (as much as possible, of course, disclaimer) and try to prevent a total explosion of the world.
Did you push out an ECS task that just will not start? The ECS Circuit Breaker will send a FAILED
event back to CloudFormation, which will roll back the ECS service to the previously working task definition.
Did you try to deploy many interconnected resources, one of which has a broken configuration? You go back to a clean slate (and yes, this is a pro for me).
This feels really like trying to deploy a new version of a service, and you have a health check that fails. Voila’, we roll back instead of keeping broken infrastructure around!
Like A LOT: IAM helpers
bucket.grantRead(function);
table.grantReadWriteData(this.handler);
lambda.grantInvoke(this.handler);
🤤
And, in general, the overall IAM scene is just pretty.
lambda.addToRolePolicy(
new aws_iam.PolicyStatement({
effect: Effect.ALLOW,
actions: ['sns:Publish'],
resources: [snsTopic.topicArn]
})
);
Also, the Arn
helpers are pure gold.
snsTopicForAlarms = aws_sns.Topic.fromTopicArn(
this,
'SNSTopicForAlarms',
Arn.format(
{
service: 'sns',
resource: 'topic',
resourceName: props.snsTopicForAlarmsName
},
this
)
);
Like: Lambdas and events are at the core of the world
The more I use the AWS CDK, the more I realize you find Lambdas everywhere, and the more I want to use Lambda functions, especially in the DevOps context.
When your CDK project is written in the same language you normally speak (say TypeScript for me), the fact that I can write a Lambda that interacts with my AWS infrastructure in TypeScript, and it takes a real few lines of code to set that up, made my life easy.
I perceive AWS development as a mix of infrastructure code and Lambda event-driven interactivity. Have you added a new AWS account to your organization, and do you want to receive a Slack notification? Ez! a little EventBridge
-> Lambda
-> AWS ChatBot
pipeline will do the trick.
Are you trying to build a deployed services catalog? EventBridge -> Lambda -> DynamoDB

EventBridge! EventBridge! EventBridge!
Although all of this is not strictly CDK-related, the AWS CDK makes it insanely easy to set up such pipelines, code, and related infrastructure because everything is in one place.
Like, LOVE: Testing snapshots
Someone will hate my ideas here. But they’re mine, so I like them.
I love test snapshots. They are the most immediate way to spot differences in your deployments and ensure that nothing weird changes at unexpected times.
As easy as this:
// Init stack
const stack = new KiwiStack(app, 'MyStack');
const template = Template.fromStack(stack);
expect(template).toMatchSnapshot();
- Do you fear your CI environment behaves differently than your laptop? Use a snapshot.
- Does a Dependabot update cause a new version of some library you are using in a Lambda function, which means you would be releasing a new version of the function? Caught! And yes, this is another thing I see as a pro because it is easy to fix a test but hard to spot when something breaks because you just don’t know it got updated.
Snapshot name: `KiwiStack 1`
- Snapshot - 1
+ Received + 1
@@ -302,11 +302,11 @@
"Properties": {
"Code": {
"S3Bucket": {
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
},
- "S3Key": "6c1e9b465fa4b2d651dbc9ce3e732d8702a7b19137327684a71d89f1d355f1a2.zip",
+ "S3Key": "2eb6a831b107939f63cfebf68e6316e1a40f79fc99cae0fee9b333bac8d29bc4.zip",
},
"Description": {
"Fn::Join": [
"",
[
11 | // Prepare for assertions
12 | const template = Template.fromStack(stack);
> 13 | expect(template).toMatchSnapshot();
| ^
14 |
15 | template.resourceCountIs('AWS::S3::Bucket', 1);
16 | template.resourceCountIs('AWS::CloudFront::Distribution', 1);
Conclusion
I’m an AWS CDK fanboy.
You can find me on LinkedIn!