38.8 CDK vs CloudFormation vs Terraform: Choosing the Right Tool
Alright, let’s cut through the marketing fluff and talk about what these tools actually are and, more importantly, which one you should use to stop hating your life when deploying to AWS.
First, a crucial bit of context: CloudFormation, Terraform, and the CDK aren’t all playing the same game. It’s less like choosing between three different brands of hammer and more like choosing between a raw lump of iron, a standard hammer, and a fancy pneumatic nail gun that also makes you coffee.
CloudFormation is AWS’s native infrastructure-as-code service. It’s the bedrock. Everything else either compiles down to it or desperately wishes it could talk to AWS as natively. You write JSON or YAML templates that describe your desired end state. AWS’s CloudFormation service then takes that template and makes it so. Its biggest strength is its deep, first-party integration with AWS—new services and features appear here first. Its biggest weakness is that writing complex, reusable infrastructure in YAML is a special kind of torture designed to make you appreciate the small things in life, like stubbing your toe or getting a paper cut.
Terraform, from HashiCorp, is a cloud-agnostic Infrastructure as Code tool. You write configuration files in HCL (HashiCorp Configuration Language), which is basically JSON but with slightly more human-friendly syntax. Terraform then uses providers (plugins) to talk to cloud providers (AWS, Azure, GCP) and manage resources. Its state is stored in a file, which is both its superpower and its Achilles’ heel.
The AWS CDK is the new kid on the block, and it’s a complete game-changer. You write your infrastructure definitions using real, actual programming languages (TypeScript, Python, Java, etc.). The CDK code is then synthesized or compiled into a CloudFormation template, which is deployed just like any other. This means you get to use loops, conditionals, functions, and classes to create reusable, composable, and testable infrastructure components. It’s infrastructure-as-software.
The Core Differentiator: Declarative vs. Imperative
This is the philosophical heart of the matter. CloudFormation and Terraform are declarative. You describe the what: “I want a VPC with these subnets.” The engine figures out the how.
The CDK is imperative. You write code that instructs the CDK library on how to declare what you want. You’re essentially programming the generation of a declarative template. This is why it’s so powerful. Need to create a S3 bucket for each item in an array? Just map over the array and instantiate a new Bucket. Try doing that elegantly in YAML. I’ll wait.
// CDK (TypeScript) - Imperative
const bucketNames = ['app-data', 'app-logs', 'backups'];
bucketNames.forEach(name => {
new s3.Bucket(this, name, {
bucketName: `my-app-${name}-${accountId}`,
encryption: s3.BucketEncryption.S3_MANAGED,
});
});
The above code synthesizes into a CloudFormation template that declares three separate AWS::S3::Bucket resources. You used a programming construct (a loop) to avoid repetitive code.
State Management: The Secret Sauce and Single Point of Failure
This is where Terraform and CloudFormation diverge sharply.
CloudFormation manages state for you. It’s a fully managed service. When you create a stack, AWS keeps a record of every resource in it, their status, dependencies, and outputs. You never see this state file; it’s locked away in AWS’s vault. This is fantastic because it’s one less thing for you to manage. It’s also terrifying because if you delete a stack, that state is gone, and managing complex inter-stack dependencies requires careful thought.
Terraform manages state in a file called terraform.tfstate. This file is a JSON dump of what it thinks your infrastructure looks like. By default, it’s stored locally, which is a recipe for catastrophic disaster if you’re on a team. You must configure remote state (e.g., in an S3 bucket) so your entire team is working from a single source of truth. This is a manual step, and if you mess it up, you will have a bad time. Terraform uses this state to map your config to real-world resources, making it incredibly powerful for managing non-AWS resources or complex multi-cloud setups, but it’s a responsibility you must take seriously.
The CDK, since it compiles to CloudFormation, inherits CloudFormation’s state management. The CDK toolkit itself also maintains a bit of metadata (like which version of the toolkit synthesized the template) in a file called cdk.context.json, but the authoritative state is always with CloudFormation.
Ecosystem and Portability: The Lock-in Debate
Let’s be direct: The CDK is all-in on AWS. It’s the antithesis of portable. If you’re writing a CDK app, you are marrying AWS. This isn’t a bad thing if, like most of us, you’re operating in a predominantly AWS environment. You get access to every service and feature with a level of elegance the others can’t match.
Terraform wins the portability crown. The same HCL language and terraform plan command can be used to manage AWS, a Kubernetes cluster, your Cloudflare DNS, and a Datadog monitor. If multi-cloud is a hard requirement, Terraform is your only sane choice among these three.
CloudFormation is, obviously, AWS-only. While there are projects like cfn-flip and former tools like AWS’s now-deprecated Former2, porting a complex CloudFormation template to another cloud is a brutal, largely manual process.
The Developer Experience: Where the CDK Absolutely Smokes the Competition
This isn’t even a fair fight. Writing infrastructure in a full-fledged programming language is a transformative experience.
Intelligent Defaults (The “Good Opinionated” Stuff): The CDK constructs are brilliantly designed with smart defaults. Want a Lambda function? The CDK will automatically create a basic IAM role for it. Creating an API Gateway? It will set up CloudWatch logging and a default stage. You can always override these, but 80% of the time, the defaults are exactly what you need. This is the CDK team using their expertise to keep your code clean.
Discoverability and Autocomplete:
This is the killer feature. Forget trawling documentation for the correct YAML properties. Just type new s3.Bucket( and let your IDE’s autocomplete guide you through all the available options. It’s faster and you make fewer typos.
// The joy of autocomplete in your IDE
const bucket = new s3.Bucket(this, 'MyBucket', {
// Your IDE will suggest: bucketName, versioned, encryption, lifecycleRules, etc.
encryption: s3.BucketEncryption.S3_MANAGED,
lifecycleRules: [{
expiration: Duration.days(365), // <- See? Even the Duration class has helpers!
transitions: [ /* ... */ ]
}]
});
Testing: You can write unit tests for your infrastructure. Let that sink in. You can assert that a certain S3 bucket has encryption enabled, or that a Lambda function has the right timeout, without ever deploying a thing.
# Example CDK test in Python (using pytest)
def test_bucket_encrypted(self):
stack = Stack()
MyBucketConstruct(stack, "TestBucket")
template = Template.from_stack(stack)
template.has_resource_properties("AWS::S3::Bucket", {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [{
"ServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}]
}
})
So, Which One Should You Choose?
Here’s my brutally honest advice:
- Choose the AWS CDK if you are an AWS shop and your team consists of software developers who would rather write code than YAML. This is 95% of modern application teams. The productivity gains are astronomical.
- Choose Terraform if you are in a hardcore multi-cloud environment, need to manage a vast array of non-AWS services (e.g., GitHub, Datadog, PagerDuty), or have a team of dedicated infrastructure engineers who are already deeply invested in the Terraform ecosystem.
- Choose CloudFormation if… well, honestly, I struggle to find a compelling reason to write raw CloudFormation directly anymore. Maybe if you need to use a brand-new AWS service that the CDK hasn’t wrapped yet (though the CDK team is shockingly fast), or if you are working in a strictly regulated environment that requires every line of the deployed template to be pre-audited and static. Even then, you could probably use the CDK to generate that template.
The CDK didn’t just add a new tool to the box; it fundamentally changed the way we think about building cloud infrastructure. It makes the powerful, repetitive, and error-prone task of writing YAML/JSON obsolete for most use cases. Use it.