Why I switched from CloudFormation to AWS CDK after years of writing it
Introduction
If you are managing AWS infrastructure as code, the first thing that comes to mind is likely CloudFormation (hereinafter Cfn).
You write it in YAML or JSON, create a stack, and deploy it.
There was a time when I thought that was enough.
However, my perspective changed after I started using CDK.
I never expected the feeling of "programming infrastructure" to be this different.
In this article, I will summarize specifically what changes when you use CDK, focusing on a comparison with Cfn.
What is painful about CloudFormation
First, I will be honest.
You can use Cfn once you get used to it.
But it becomes painful as the scale grows.
Pain 1: YAML becomes massive
The more resources you have, the longer the files become.
500-line or 1000-line YAML files are not rare.
Similar descriptions are repeated, and it becomes unclear where you should make changes.
Pain 2: Cannot absorb repetition
When you want to create almost the same configuration for dev and prod environments, Cfn handles it with Parameters or Conditions, but the syntax is cumbersome and prone to errors.
As a result, you often end up with multiple copies of YAML files.
Pain 3: No type checking
You cannot detect errors while writing YAML.
You only realize the property name was wrong after you deploy.
This back-and-forth quietly eats up your time.
Pain point 4: Cannot write logic
“I only want to create this resource when the flag is true,” or “I want to dynamically add the environment name to the resource name”—while it's not impossible to struggle through with Cfn Conditions or !Sub, there are limits to complex conditional branching.
What is AWS CDK?
AWS CDK (Cloud Development Kit) is a framework that allows you to define AWS infrastructure using programming languages like TypeScript, Python, and Java.
Since it ultimately generates and deploys a Cfn template,the backend is the same as Cfn.
However, what you write is program code.
// CDKでVPCを作る例(TypeScript)
const vpc = new ec2.Vpc(this, 'MyVpc', {
maxAzs: 2,
natGateways: 1,
});
Writing the equivalent in Cfn would result in dozens of lines of YAML just for this.
Comparison between Cfn and CDK
Language used
CloudFormation: YAML / JSON
AWS CDK: TypeScript / Python, etc.
Amount of code
CloudFormation: High
AWS CDK: Low (it is abstracted)
Repetitive tasks
CloudFormation: Difficult (tends to involve copy-pasting)
AWS CDK: Easily handled with loops and functions
Type checking
CloudFormation: None (discovered at deployment)
AWS CDK: Yes (real-time in editor)
Conditional branching
CloudFormation: Conditions syntax (complex)
AWS CDK: Can be written naturally with if statements
Reusability
CloudFormation: Stack-based
AWS CDK: Can be modularized as Constructs
Learning cost
CloudFormation: Learn AWS syntax
AWS CDK: Programming knowledge required
Existing assets
CloudFormation: Abundant
AWS CDK: Rapidly expanding
Deployment target
CloudFormation: CFn stack
AWS CDK: CFn stack (same)
What changes when using CDK: A look at concrete examples
1. Repetitive tasks can be written with loops
When you want to create multiple S3 buckets for each environment.
In the case of Cfn:
DevBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: myapp-dev-bucket
StageBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: myapp-stage-bucket
ProdBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: myapp-prod-bucket
In the case of CDK:
const envs = ['dev', 'stage', 'prod'];
for (const env of envs) {
new s3.Bucket(this, `${env}Bucket`, {
bucketName: `myapp-${env}-bucket`,
});
}
While doing the same thing, additions, changes, and deletions can be handled in a single line.
(2) Can be modularized with Constructs
The greatest strength of CDK is the concept of "Constructs".
You can define combinations of frequently used resources as reusable components.
For example, if you bundle a set of "ECS cluster + ALB + security group" into a single Construct, you can call it in one line in other projects or stacks.
// 自作ConstructをStackで使う例
const api = new MyEcsApiConstruct(this, 'Api', {
vpc,
environment: 'prod',
containerPort: 8080,
});
You can do something similar with NestedStacks in Cfn, but because there is no benefit of types, it becomes difficult to grasp the parameters that can be passed.
(3) Real-time completion and type checking in the editor
When you write CDK in TypeScript, editors like VSCode support the following in real-time.
Property name completion (check what can be set on the spot)
Immediate detection of type errors (notice mistakes before deployment)
Convert to Cfn template with cdk synth to verify
Mistakes that were often noticed only after deployment in Cfn can now be prevented while writing.
(4) Conditional branching can be written naturally
Conditions such as enabling Deletion Protection only in the production environment can be written with standard if statements.
const isProd = props.environment === 'prod';
new rds.DatabaseInstance(this, 'DB', {
// ...
deletionProtection: isProd,
removalPolicy: isProd
? cdk.RemovalPolicy.RETAIN
: cdk.RemovalPolicy.DESTROY,
});
It is far more readable and easier to manage than using Cfn Conditions.
(5) Can choose the level of abstraction: L1 / L2 / L3
CDK has three levels for resource definition.
L1
Common name: Cfn resources
Content: 1:1 correspondence with CloudFormation. Classes with the Cfn prefix
L2
Common name: High-level Constructs
Content: Automatically includes default settings such as security groups
L3
Common name: Patterns
Content: Standard configurations that bundle sets of multiple resources
Basically, if you use L2, "best practices" such as default IAM policy settings are applied automatically.
You only need to drop down to L1 when you want fine-grained control.
Since Cfn is always equivalent to L1, you have to be aware of everything yourself, such as missing IAM policy settings.
When to use CDK and when you don't need to
When CDK is suitable
You want to reuse similar configurations across multiple environments (dev/stage/prod)
There are many resources, and you want to create reusable components
You are managing infrastructure as a team and need code that is easy to review
You are comfortable with TypeScript, Python, etc.
When sticking with Cfn is fine
Existing Cfn templates are running stably and do not need to be changed
The number of resources is small, and it can be completed with a simple configuration
There is a team rule to manage everything using only YAML
You don't have to migrate to CDK all at once.
You can start writing new resources in CDK while keeping existing ones in Cfn.
Points to keep in mind when starting with CDK
I recommend choosing TypeScript
Although CDK supports Python, Java, C#, etc., TypeScript is the easiest to work with due to the abundance of documentation and samples.
Even if you are not used to JavaScript, the type system provides autocompletion, making it surprisingly easy to write.
Get into the habit of reading the YAML generated by cdk synth
When you are not used to it, it is easy to end up in a state where you don't know what is being created by the Cfn templates that CDK automatically generates.
By regularly checking the output of cdk synth, you can verify that no unintended resources are being created.
You can automate security checks with cdk-nag
By using a library called cdk-nag, you can detect issues like lack of least privilege in IAM or missing encryption settings at the code level.
It is effective for reducing the cost of security reviews.
Summary
CloudFormation is still a powerful and active tool.
However, as the scale grows, the limitations of "repetition," "conditional branching," and "modularization" become apparent.
CDK is an approach that overcomes these limitations through the power of programming.
Since the underlying mechanism is the same as Cfn, you can migrate while leveraging your existing knowledge.
If you can write Cfn but haven't tried CDK yet, I encourage you to start by writing one new resource in CDK.
Once you get used to it, you won't be able to go back to Cfn.
If you found this article helpful, please give it a like or follow me; it would be a great encouragement.
