TL;DR
When my team chose AWS CDK for my recent AWS integration project, I didn’t fully appreciate how different it would feel from CloudFormation or Terraform. This post breaks down an honest comparison of all three tools, what each one does well, where each one costs you, and a practical decision framework so you can pick the right IaC tool for your next AWS project.
Table of Contents
Introduction
Have you ever inherited a 2,000-line CloudFormation YAML file and wondered if there is a better way? I have and that question led me to properly work through the AWS CDK vs Terraform vs CloudFormation decision on a real, production-grade AWS project.
On a recent enterprise AWS integration platform, I built a config-driven pipeline using CDK: roughly 180 CloudFormation resources across three logical stacks, covering API Gateway, EventBridge, Step Functions, DynamoDB, S3, SQS, Secrets Manager, and a CloudWatch dashboard. The choice of IaC tooling genuinely shaped how we built, tested, and shipped the platform.
In my blog, you will learn:
- How AWS CDK, CloudFormation, and Terraform compare on real-world AWS workloads
- What each tool is best suited for
- A structured decision framework to help you choose the right one for your team
AWS CDK vs Terraform vs CloudFormation: Feature comparison
| AWS CloudFormation | Terraform | AWS CDK |
Languages | JSON / YAML | HCL | TypeScript, Python, Java, Go, C# |
State management | Managed by AWS | Local / remote `.tfstate` | Managed by AWS (via CloudFormation) |
AWS coverage | 100%, day-one | Near-complete, slight lag | 100% (L1), growing L2/L3 |
Multi-cloud | AWS only | Yes | AWS only |
Learning curve | Low | Medium | Higher |
Abstraction level | None | Moderate (modules) | High (constructs, patterns) |
The key differences come down to abstraction and ownership: CloudFormation gives full AWS-native control with zero abstraction; Terraform delivers the best multi-cloud portability through HCL modules and mature state management; AWS CDK lets you write real programming logic to synthesise CloudFormation, making it the most expressive choice for complex AWS workloads. Your decision depends on team skills, cloud footprint, and infrastructure complexity.
AWS CloudFormation: Reliable, but does it scale?
AWS CloudFormation is the foundation every other AWS IaC tool builds on. CDK, SAM, and Service Catalog all synthesise down to CloudFormation in the end. If you are new to infrastructure as code, the Infrastructure as Code 101 guide is a great starting point before diving into tool comparisons.
What works well
- Day-one AWS support: Every new service and property ships with CloudFormation support immediately, no waiting for a provider update.
- No state file: AWS manages all state internally. There’s no `.tfstate` to secure, version, or accidentally corrupt.
- Change sets: Before deploying, you preview exactly what will be created, modified, or replaced, with automatic rollback on failure.
Where it struggles
- YAML at scale is painful: A single Lambda function requires separate `AWS::Lambda::Function`, `AWS::IAM::Role`, and `AWS::IAM::Policy` resources, all written by hand. Multiply that across 15+ functions and you’re maintaining thousands of lines of near-identical YAML.
- No real control flow: `!If`, `!FindInMap`, and `Conditions` exist, but creating five similar DynamoDB tables means writing five full resource blocks. There is no loop.
- 500 resource limit per stack: Splitting stacks manually and wiring them with `Outputs` and `Fn::ImportValue` is brittle and you can’t remove a cross-stack reference without a multi-step tear-down.
Bottom line: CloudFormation is excellent for small, stable infrastructure. It doesn’t scale well for large integration platforms.
Terraform: Best for Multi-Cloud teams?
Terraform uses HCL and keeps a state file representing your infrastructure’s current reality.
What works well
- Multi-cloud is first-class: A single workspace can provision resources across AWS, Azure, and GCP, something neither CloudFormation nor CDK can do natively.
- terraform plan is outstanding: You get a precise, human-readable diff of what will change before anything is deployed. For teams, this builds trust quickly.
- Modules are composable: The Terraform Registry has quality community modules for common patterns, similar in spirit to CDK L2/L3 constructs but without compile-time type checking.
- terraform import is mature: Bringing unmanaged AWS resources under Terraform control is a first-class, well-documented workflow.
Where it struggles
- HCL isn’t a real programming language: `for_each`, `count`, and dynamic blocks cover most cases, but complex conditional logic across multiple config objects becomes genuinely messy.
- The state file is a liability: It must be stored securely (it can contain sensitive values), versioned, and locked during concurrent runs. Teams need a remote backend (S3 + DynamoDB locking or Terraform Cloud). This is solved, but it’s operational overhead that CloudFormation users never face.
- AWS provider lag: New AWS features can take weeks to land in the `hashicorp/aws` provider.
Bottom line: Terraform is the right call when your organisation already has Terraform expertise or genuine multi-cloud requirements.
AWS CDK: The Case for Infrastructure as Real Code
CDK transpires TypeScript (or Python, Java, etc.) into CloudFormation templates. Underneath it’s still CloudFormation. CDK is a synthesis-time tool, not a separate deployment engine.
The construct model
CDK has three abstraction levels:
- L1 (`Cfn*`): Auto-generated CloudFormation wrappers. Full control, no abstraction.
- L2: Opinionated constructs with smart defaults: `TableV2`, `NodejsFunction`, `StateMachine`.
- L3 (Patterns): Multi-resource patterns. One call to ApplicationLoadBalancedFargateService creates the full set of resources. The Cloud Lego with AWS CDK post explores this composability in depth.
Where CDK shone on my recent project
Real control flow for multi-environment config: In CDK, environment differences are plain TypeScript: readable, type-checked, and refactorable:
In CloudFormation this is a `Mappings` block with `!FindInMap` calls everywhere. In Terraform it’s `lookup (var.config_map, var.stage)`. TypeScript is more readable and catches typos at `cdk synth`, not at runtime.
Type-safe cross-stack references: Rather than passing string ARNs between stacks, CDK lets you pass typed construct objects directly:
If the types don’t match, it fails at synth time and not halfway through a deployment.
grant methods eliminate IAM boilerplate: Rather than authoring `AWS::IAM::Policy` resources with explicit ARNs, CDK L2 constructs expose one-liners:
Across 15+ Lambda functions, this removed roughly 300 lines of IAM boilerplate.
Lambda bundling is built-in: `NodejsFunction` uses esbuild to compile TypeScript, tree-shake dependencies, and produce a ZIP artifact during `cdk synth`. No separate build pipeline or webpack config needed.
The real costs of CDK
- Bootstrap is required: You must run `cdk bootstrap` before first deployment, which provisions an S3 bucket and ECR repo in your AWS account. Neither CloudFormation nor Terraform has this prerequisite.
- Synthesised templates are unreadable: Generated logical IDs like `Lambda-function-role-name-<random-string-ID>` make debugging in the CloudFormation console painful.
- Version churn: CDK releases frequently, and some constructs aren’t yet L2. EventBridge Scheduler, for example, required a drop to L1 `CfnSchedule`. Pin your CDK version in `package.json`.
- You’re still on CloudFormation’s clock: Deployment speed, rollback behaviour, and the 500-resource limit all still apply. CDK doesn’t fix slow CloudFormation stacks.
AWS CDK vs Terraform vs CloudFormation: Which IaC Tool should you choose?
Choose CloudFormation if:
- Your team is AWS-native
- Infrastructure is small, relatively static, and does not require loops or conditionals
- You want zero external tooling dependencies and native AWS rollback support
- You are managing a small number of resources where YAML verbosity is not yet a bottleneck
Choose Terraform if:
You need to provision resources across multiple cloud providers (AWS + Azure or GCP)
– Your organisation already has mature Terraform modules, state backends, and pipelines
– You need `terraform import` to bring existing unmanaged infrastructure under IaC
– Your team has strong HCL experience and prefers not learning a general-purpose language
Choose AWS CDK if:
– Your team writes TypeScript, Python, or Java and wants type safety at infrastructure level
– Your project has many Lambda functions, complex IAM policies, or multi-environment config
– You want cross-stack references enforced by the compiler — not by naming conventions
– Reusability through constructs and patterns matters more than keeping templates readable
Real-world outcome
On the enterprise AWS integration project described in this post, CDK was the clear choice:
- 15+ Lambda functions defined with shared config, bundling, and IAM in TypeScript
- Three logical stacks wired via typed construct references with no string ARNs passed between stacks
- ~300 lines of IAM boilerplate eliminated using grant* methods
- Custom Resource seeding step is straightforward in TypeScript, painful in raw YAML
This would have been 3,000+ lines of hand-crafted YAML in CloudFormation, with none of the refactoring safety net.
The right tool is the one your team will maintain confidently over time. The business value comes not from the tool itself, but from the consistency, repeatability, and speed of delivery it enables and from keeping your engineers focused on product problems, not YAML indentation.
If you are starting a new AWS project and unsure where to begin, reach out to the team at Cevo — we have helped organisations of all sizes navigate IaC tooling decisions and deliver cloud infrastructure at scale.

Saisha Hardikar is a Data Engineer at Cevo, specialising in both data and DevOps projects, with a focus on cloud-native architecture and AWS integrations. They have delivered enterprise AWS platforms using CDK, CloudFormation, and Terraform across multiple industries.



