If you're a software developer managing AWS-only infrastructure, CDK will almost certainly make you more productive. If you're a platform engineer managing multi-cloud or working alongside an ops-heavy team, Terraform is probably the right call.
That's the short answer. But the infrastructure as code decision is a 3-5 year commitment, and two events in 2025 changed the calculus in ways most comparisons haven't caught up to: CDKTF was deprecated by HashiCorp (eliminating the "best of both worlds" escape hatch), and CDK gained a series of major features (CDK Toolkit Library, CDK Refactor, and CDK Mixins) that widen the gap against Terraform for AWS-native teams.
Here's what this guide gives you: a specific recommendation for your situation, a performance benchmark you can reference, side-by-side code showing the real abstraction difference, a migration path if you need to switch tools, and an honest answer to the CDKTF question.
I've deployed production CDK stacks across client engagements and maintained both CDK and Terraform codebases. The differences I describe below are practical, not theoretical.
Executive Summary: Which Tool Should You Choose?
Let me give you the direct answer before we dig into the details.
Quick Recommendation Matrix
Choose AWS CDK if:
- Your infrastructure is 100% AWS
- Your team are developers who prefer TypeScript, Python, Java, C#, or Go
- You want sensible defaults and high-level abstractions (L2/L3 constructs)
- You don't need multi-cloud support now or in the foreseeable future
Choose Terraform if:
- You need multi-cloud support (AWS, Azure, GCP)
- You prefer declarative configuration over programming
- You have existing Terraform expertise and modules
- Your ops team (not developers) manages infrastructure
Consider both if: You have complex use cases where CDK handles application infrastructure and Terraform manages foundational or multi-cloud resources. This adds real complexity, so only go hybrid if you have specific reasons.
TL;DR: Key Takeaways
- Neither is "better": they're optimized for different scenarios
- CDK = AWS-native programming with automatic CloudFormation-managed state, rich L2/L3 abstractions, and a fast-moving 2025-2026 feature set (Toolkit Library, Refactor, Mixins)
- Terraform = multi-cloud declarative with manual state management and a mature BSL-licensed ecosystem
- CDKTF is dead: HashiCorp deprecated it in December 2025; that exit ramp is closed
- CDK deploys via CloudFormation, which is roughly 2-3x slower than Terraform for the same resource set due to the intermediary service layer
- Hybrid is valid but complex: use it only with clear team and resource boundaries
If you want more depth to validate this recommendation, the comparison table below shows the key technical differences.
The Quick Comparison Table
Here's a side-by-side view of the major differences, now including Pulumi:
| Feature | AWS CDK | Terraform | Pulumi |
|---|---|---|---|
| Languages | TypeScript, Python, Java, C#, Go, JavaScript | HCL (HashiCorp Configuration Language) | TypeScript, Python, Go, .NET, Java, YAML |
| Cloud Support | AWS only | Multi-cloud (3,000+ providers) | Multi-cloud (50+ providers) |
| State Management | Automatic via CloudFormation | Manual configuration (S3 backend recommended) | Pulumi Cloud or self-managed backend |
| Deployment Engine | CloudFormation | Terraform engine (direct AWS API calls) | Pulumi engine (direct AWS API calls) |
| Rollback | Automatic on failure | Manual intervention required | Manual intervention required |
| Testing | Built-in assertions module | Third-party tools (Terratest, etc.) | Built-in testing support |
| Setup Required | Bootstrap per account/region | Backend configuration per project | No bootstrapping required |
| Abstractions | L1, L2, L3 constructs + Mixins | Flat module structure | Flat component structure |
| Current Version | Construct Library 2.243.0, CLI 2.1000+ | BSL (since August 2023) | Apache 2.0 (open source) |
| Licensing | Apache 2.0 (open source) | Business Source License (BSL) | Apache 2.0 (open source) |
| Learning Curve (Devs) | Low (familiar languages) | Medium (learn HCL) | Low (familiar languages) |
| Learning Curve (Ops) | Medium (requires programming) | Low (declarative config) | Medium (requires programming) |
| Drift Detection | cdk drift (wraps CloudFormation detection) | terraform plan / terraform refresh | pulumi refresh |
Why these differences matter: CDK generates CloudFormation templates and uses AWS's deployment engine. Terraform and Pulumi both call AWS APIs directly. This architectural difference affects provisioning speed, rollback behavior, and how you debug failures.
Understanding the Fundamentals
Before comparing these tools, you need to understand what they actually do under the hood. Both are Infrastructure as Code tools, but they approach the problem from different angles.
What is AWS CDK?
AWS CDK (Cloud Development Kit) is an open-source framework that lets you define cloud infrastructure using familiar programming languages. Your CDK code is synthesized into CloudFormation templates, which CloudFormation then deploys to provision resources.
Key characteristics:
- Supports six languages: TypeScript, JavaScript, Python, Java, C#, and Go
- Synthesizes to CloudFormation: CDK isn't a replacement for CloudFormation. It's a layer on top
- Three levels of constructs for different abstraction needs, plus CDK Mixins (GA March 2026) for cross-cutting features
- Version structure: Since February 2025, the CDK CLI (now at 2.1000+) and Construct Library (currently at 2.243.0) follow independent release cadences, so the version numbers will look mismatched, but that's intentional
The construct model is the heart of CDK:
- L1 (CFN Resources): Direct 1:1 mapping to CloudFormation resources (e.g.,
CfnBucket). No abstraction, complete control - L2 (Curated Constructs): Opinionated resources with sensible defaults and best practices built in (e.g.,
s3.Bucket). Most commonly used - L3 (Patterns): Multi-resource architectures that create complete solutions (e.g.,
ApplicationLoadBalancedFargateServicecreates ECS Fargate + ALB with one construct) - Mixins: Cross-cutting features (encryption, auto-delete, compliance settings) applied to any L1 or L2 construct via
.with(), available as of March 2026
If you want to dig into CDK fundamentals, check out my beginner's guide to AWS CDK.
What is Terraform?
Terraform is a platform-agnostic Infrastructure as Code tool using HashiCorp Configuration Language (HCL). It manages infrastructure through its own deployment engine and state files, supporting multiple cloud providers and services.
Key characteristics:
- Uses HCL: A declarative configuration language designed to be human-readable
- Platform-agnostic: Supports AWS, Azure, GCP, and 3,000+ other providers through plugins
- State files: JSON files that track your deployed infrastructure (critical for knowing what exists)
- Own deployment engine: Terraform handles deployments by calling cloud APIs directly, not through CloudFormation
The Terraform workflow:
terraform init- Initialize working directory, download providersterraform plan- Preview what changes will be appliedterraform apply- Provision or update infrastructureterraform destroy- Remove all managed infrastructure
The Imperative vs Declarative Paradigm
Here's the fundamental difference in how you think about infrastructure:
CDK is imperative: you write code that describes HOW to create infrastructure using programming logic (loops, conditionals, functions). You have full programming capabilities at your disposal.
Terraform is declarative: you write configuration that describes WHAT infrastructure should exist, not how to create it. The tool figures out how to get there.
Let me show you the difference with a simple S3 bucket:
// CDK (TypeScript) - imperative approach
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as cdk from 'aws-cdk-lib';
const bucket = new s3.Bucket(this, 'MyBucket', {
encryption: s3.BucketEncryption.S3_MANAGED,
versioned: true,
removalPolicy: cdk.RemovalPolicy.RETAIN
});
# Terraform (HCL) - declarative approach
resource "aws_s3_bucket" "my_bucket" {
bucket = "my-unique-bucket-name"
}
resource "aws_s3_bucket_versioning" "my_bucket" {
bucket = aws_s3_bucket.my_bucket.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "my_bucket" {
bucket = aws_s3_bucket.my_bucket.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
Notice how CDK's L2 construct handles encryption and versioning with simple properties, while Terraform requires separate resource blocks. This is the abstraction benefit of CDK's constructs, but it also means less explicit visibility into what's being created.
Neither paradigm is superior. They serve different mental models and team skillsets.
CDK vs Terraform vs Pulumi: The Third Option
Most comparisons treat this as a two-horse race. But search trends show a meaningful volume of queries for "AWS CDK vs Terraform vs Pulumi," and since CDKTF is now deprecated, Pulumi is the answer teams reach for when they want programming languages plus multi-cloud support.
Pulumi isn't the right default, but it belongs in your decision. Here's how to think about where it fits.
Where Pulumi Fits
Pulumi uses general-purpose programming languages (TypeScript, Python, Go, .NET, Java, YAML) across more than 50 cloud providers. State is managed via Pulumi Cloud (managed service) or a self-hosted backend, which is neither the automatic CloudFormation approach of CDK nor the fully manual setup of Terraform.
AWS Prescriptive Guidance describes Pulumi this way: if your organization can tolerate a high level of risk and needs to support multi-cloud or hybrid-cloud environments, consider Pulumi. The "high level of risk" qualifier is significant: it refers to Pulumi's smaller community relative to Terraform, which means fewer battle-tested modules, less StackOverflow coverage, and a steeper debugging path when things go wrong.
Pulumi is licensed under Apache 2.0, which is one concrete advantage over Terraform's BSL since CDKTF was deprecated.
When to Consider Each
Here's the decision logic:
The short version:
- AWS-only and your team writes code: CDK wins
- Multi-cloud and your team writes code: Pulumi is worth evaluating
- Multi-cloud and your team prefers declarative config: Terraform
- Mixed team with no clear IaC owner: Terraform's HCL is the neutral middle ground
Now that you know where Pulumi fits, let's return to the primary comparison and start with what's changed in CDK since most comparison articles were written.
What Changed in 2025-2026: CDK's New Features
People searching for "aws cdk latest version 2026" and "aws cdk updates 2026" are landing on this post without finding the answer. This section fixes that.
AWS CDK shipped four significant features in the 15 months between February 2025 and March 2026. None of these appear in competitor posts yet. If you're reading a comparison article that doesn't mention CDK Mixins or CDK Refactor, you're reading outdated information.
| Date | Feature | What It Solves |
|---|---|---|
| February 2025 | CLI/Construct Library version split | CLI and library now release independently; CLI jumps to 2.1000+ range while Construct Library continues 2.x numbering |
| May 2025 | CDK Toolkit Library (GA) | Programmatic CDK operations without CLI subprocess calls |
| September 2025 | CDK Refactor (Preview) | Rename constructs or move them between stacks without resource replacement |
| March 2026 | CDK Mixins (GA) | Apply cross-cutting features to any construct via .with() without subclassing |
CDK Toolkit Library: Programmatic CDK Without CLI Subprocesses
The CDK Toolkit Library (@aws-cdk/toolkit-lib) gives you programmatic access to CDK operations (synth, deploy, rollback, destroy, watch) without spawning CLI subprocess calls. Install with:
npm install --save @aws-cdk/toolkit-lib
This matters for teams building custom CI/CD workflows, automation tooling, or ephemeral environment managers. Instead of shelling out to cdk deploy from your pipeline code, you call the library directly. It also opens the door to programmatically enforcing guardrails: your CI tool can inspect the synthesized template before allowing deployment.
CDK Refactor: Safe Code Restructuring Without Resource Replacement
This one is a big deal for anyone who has ever had a CDK stack grow organically and then wanted to reorganize it. Before CDK Refactor, renaming a construct or moving it between stacks caused CloudFormation to treat it as a new resource, which means deleting and recreating the old one. For stateful resources (databases, S3 buckets, SQS queues), that's a production incident waiting to happen.
The cdk refactor command (using --unstable=refactor) detects when resources have been moved or renamed, then uses CloudFormation's refactoring API to update logical IDs without touching the underlying physical resources. It verifies your CDK app contains exactly the same set of resources as the deployed state before proceeding.
One constraint: if you need to add, delete, or modify resources at the same time as a refactor, you have to split those into separate deployments. It's a meaningful limitation, but it's a lot better than losing data.
CDK Mixins: Cross-Cutting Features Without Subclassing
CDK Mixins solve a specific pain point: applying the same compliance or security settings (encryption, auto-delete, block public access) across many constructs without creating a sprawling inheritance hierarchy.
Before Mixins, you had three options when an L2 construct didn't support a feature natively: use an L1 construct directly, apply raw property overrides, or create a CloudFormation custom resource. Mixins add a fourth option: apply the feature via .with() on any L1 or L2 construct:
new s3.CfnBucket(scope, 'MyL1Bucket')
.with(new BucketBlockPublicAccess())
.with(new BucketAutoDeleteObjects());
You can also combine multiple Mixins into a custom compliance policy and apply it across an entire scope. For teams building internal CDK libraries with governance requirements, this is a meaningful reduction in boilerplate. See my post on CDK best practices for production for how to apply this in practice.
These CDK updates matter specifically to the CDKTF deprecation story, because some users were on CDKTF precisely to get the programming language experience that CDK now delivers natively on AWS.
The CDKTF Deprecation: What It Means for Your Decision
If you searched "AWS CDK for Terraform," you're in the right place: that's CDKTF, and this section explains exactly what happened to it.
What Was CDKTF?
CDK for Terraform (CDKTF) was HashiCorp's attempt to combine CDK's programming language benefits with Terraform's multi-cloud capabilities. Built on the JSII library (the same engine that powers AWS CDK), it enabled writing Terraform configurations in C#, Python, TypeScript, Java, or Go.
The workflow: define infrastructure using CDK-style constructs, CDKTF generates Terraform HCL configurations, then deploy using standard Terraform workflows.
It promised the best of both worlds: familiar programming languages with Terraform's multi-cloud provider ecosystem.
Why HashiCorp Deprecated It
HashiCorp deprecated CDKTF in December 2025. The likely reasons include:
- Maintenance burden of keeping two abstraction layers in sync
- Limited adoption compared to native Terraform
- Complexity that didn't justify the benefits
- Strategic focus on the core Terraform product
One nuance worth noting: as of March 2026, AWS documentation still references CDKTF as a viable tool; AWS prescriptive guidance hasn't been updated to reflect the HashiCorp-side deprecation decision. So if you're reading AWS docs and see CDKTF mentioned, that reflects documentation lag, not a resurrection.
What to Do If You're Currently on CDKTF
Existing CDKTF projects continue to work; the deprecation means no new features or updates, not immediate breakage. There's no forced migration deadline.
But you should plan a migration path. Here's the decision:
- If your infrastructure is AWS-only: migrate to native AWS CDK. Your programming language skills transfer directly. CDKTF constructs closely resemble CDK constructs; the main work is replacing CDKTF provider constructs with CDK L1/L2 equivalents. The new CDK features (Mixins, Refactor, Toolkit Library) make native CDK more capable than CDKTF ever was for AWS workloads.
- If you genuinely need multi-cloud: evaluate Pulumi. It's architecturally the closest successor: programming languages, multi-cloud, actively maintained, and Apache 2.0 licensed. The community is smaller than Terraform's, but it's the right answer if you need both programming languages and multi-cloud.
- If your team prefers declarative: move to native Terraform HCL. CDKTF was generating HCL anyway, so the migration removes an abstraction layer and gives you full Terraform ecosystem access.
The key question: were you on CDKTF primarily for the programming language experience, with AWS-focused infrastructure? If yes, CDK is your answer.
Implications for Tool Selection Today
Don't choose CDKTF: it's deprecated and won't evolve.
The "best of both worlds" doesn't exist anymore. You have to choose between CDK (AWS-only) or Terraform/Pulumi (multi-cloud).
This validates the binary choice we're discussing: if HashiCorp couldn't make the synthesis approach work well, hybrid approaches are likely complex.
Now that the CDKTF question is settled, let's address the second recent change affecting Terraform: its licensing shift.
The Licensing Question: Terraform's BSL and What It Means
Terraform's License Change Explained
Terraform moved from Mozilla Public License (open source) to Business Source License (BSL) in August 2023. This is not a minor change.
BSL restricts commercial use in certain scenarios, specifically around creating competing products. The license automatically converts to open source after a specified time period.
AWS Prescriptive Guidance documents this directly as a disadvantage of Terraform: "In August 2023, HashiCorp made an announcement that it would no longer be licensed as open source under the Mozilla Public License. Instead, it is now licensed under the Business Source License."
Practical Implications for Your Organization
For most users: No change. You can still use Terraform freely for infrastructure management.
For enterprises: Review with your legal team if you're a cloud provider or building competing IaC products.
For open-source advocates: The loss of pure open-source licensing may be a philosophical concern.
Future uncertainty: HashiCorp could change license terms again.
The Fundamental Business Model Problem
Here's a perspective that often gets overlooked in technical comparisons.
AWS's position: AWS doesn't need to monetize CloudFormation or CDK directly. They make money when you run resources on AWS. CDK exists to help you deploy infrastructure faster, safer, and more automatically, which means more AWS consumption.
AWS's aligned incentives: The better CDK is, the more AWS resources you'll deploy. Their business model aligns with your success.
HashiCorp's dilemma: No cloud infrastructure revenue means they must monetize the tooling itself. This is why Terraform Cloud exists and why the license changed.
Why the license changed: Other companies (Spacelift, env0, Scalr) were building commercial products on top of open-source Terraform, threatening HashiCorp's revenue.
Strategic risk: When your IaC vendor's survival depends on monetizing the tool, future changes may not align with your interests. AWS will continue investing in CDK because it drives their core business. This stability doesn't exist for Terraform.
This isn't a technical limitation; it's a business risk factor for long-term infrastructure decisions.
OpenTofu as an Alternative
OpenTofu is a Terraform fork created in response to the BSL change, backed by the Linux Foundation. AWS's acknowledgment of the BSL impact is visible in a concrete operational change: AWS Service Catalog replaced all references to "Terraform Open Source" as a product type with "External," a generic label that now covers OpenTofu and any other Terraform-compatible tool. AWS Control Tower made the same transition in 2023.
AWS doesn't explicitly name OpenTofu in its documentation, but the "External" product type signal is clear. If you're using AWS Service Catalog with Terraform, you'll need to migrate existing products to the External product type.
Consider OpenTofu if open-source licensing is critical to your organization. Trade-off: smaller ecosystem and less mature than either Terraform or CDK.
Provisioning Speed: CDK vs Terraform vs Pulumi
This is one of the most common questions in the AWS CDK vs Terraform comparison, and it gets very few honest answers. Let me give you one.
There are no AWS-published benchmarks comparing CDK, Terraform, and Pulumi provisioning speeds. Anyone citing specific numbers as "official" is making them up. But the architectural reason for the speed difference is well-documented and explainable, and the practical difference is real.
Why CDK Deploys Through CloudFormation
CDK does not provision resources itself. Here's the actual deployment chain:
cdk synthruns locally: CDK code compiles to CloudFormation JSON/YAML templates (this step is fast, happens on your machine)- CDK CLI uploads assets to S3/ECR in the bootstrapped environment
- CDK CLI submits the CloudFormation template to CloudFormation service
- CloudFormation provisions your AWS resources
The synthesis step (step 1) is local and fast. Steps 2-4 involve the CloudFormation service as an intermediary, and CloudFormation adds overhead because it's managing state, processing events, and handling rollback logic internally. CloudFormation does manage resource parallelism automatically, but the service overhead is real.
How Terraform and Pulumi Deploy Directly
Terraform and Pulumi skip the intermediary:
terraform plancomputes changes by comparing state and target configurationterraform applycalls AWS APIs directly in dependency order, provisioning resources in parallel where no dependencies exist
There's no CloudFormation service involved. Terraform talks directly to the AWS API. This is architecturally faster because you remove an entire service layer from the critical path.
Benchmark Data and What to Expect
Based on deploying typical stacks across client environments, here's what I observe:
- A stack of 10-20 resources: CDK/CloudFormation typically takes 3-5 minutes; Terraform typically 1-3 minutes for the same resources
- Large, complex stacks (100+ resources): The gap narrows because CloudFormation's automatic dependency ordering and parallelism partially offset its overhead
- Lambda and ECS updates in development: CDK's
cdk watchcommand uses hot-swap deployments that bypass CloudFormation entirely for code-only changes, which is dramatically faster and partially closes the speed gap in development workflows
To be clear: these are practitioner estimates based on typical workloads, not formal benchmarks. Your actual results will vary by resource type, stack complexity, and AWS region capacity.
The bottom line: if raw provisioning speed is your primary concern, Terraform has an architectural advantage. If you're on AWS and speed matters specifically in development iteration, CDK's cdk watch feature narrows the gap considerably.
Understanding why CDK is slower leads directly to understanding its deepest technical difference from Terraform: the abstraction level. Let's look at that with actual code.
Core Differences That Matter
Now let's get into the technical differences that affect your day-to-day work.
Language and Syntax
CDK: Use your preferred programming language with full IDE support. You get autocomplete, type checking, refactoring, and all the tooling you're used to. TypeScript is the most popular choice, but Python, Java, C#, and Go are all first-class citizens.
Terraform: Learn HCL, a declarative language designed specifically for infrastructure. It's relatively simple but requires learning new syntax. IDE support exists through plugins but isn't as rich as general-purpose language tooling.
CDK advantage: Leverage existing language skills, strong typing prevents configuration errors at compile time.
Terraform advantage: HCL is easier for non-developers to read and learn. A declarative config file is more approachable than imperative code for many ops teams.
Multi-Cloud vs AWS-Native Focus
Terraform's superpower: 3,000+ providers enable multi-cloud (AWS, Azure, GCP) and SaaS integration (GitHub, Datadog, Kubernetes, etc.). If you need to manage anything beyond AWS, Terraform likely has a provider.
CDK's focus: Deep AWS integration with no multi-cloud support. You cannot deploy Azure or GCP resources with CDK.
AWS feature availability: CDK gets new AWS features when CloudFormation supports them (usually within a week). Terraform's AWS provider may lag by weeks or months for new features.
Abstraction Levels: Constructs vs Modules
This is where CDK's advantage is most visible with real code. Let me show you what an ECS Fargate service with an Application Load Balancer looks like in each tool.
CDK (L3 construct, roughly 8 lines of meaningful code):
import { ApplicationLoadBalancedFargateService } from 'aws-cdk-lib/aws-ecs-patterns';
const service = new ApplicationLoadBalancedFargateService(this, 'MyService', {
cluster,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('my-app:latest'),
},
memoryLimitMiB: 512,
cpu: 256,
desiredCount: 2,
publicLoadBalancer: true,
});
Terraform (equivalent HCL, 80+ lines):
resource "aws_ecs_cluster" "main" {
name = "my-cluster"
}
resource "aws_ecs_task_definition" "app" {
family = "my-app"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = 256
memory = 512
execution_role_arn = aws_iam_role.ecs_execution.arn
container_definitions = jsonencode([{
name = "app"
image = "my-app:latest"
portMappings = [{
containerPort = 80
protocol = "tcp"
}]
}])
}
resource "aws_lb" "main" {
name = "my-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = var.public_subnet_ids
}
resource "aws_lb_target_group" "app" {
name = "my-app-tg"
port = 80
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "ip"
}
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.main.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
resource "aws_ecs_service" "app" {
name = "my-app"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.app.arn
desired_count = 2
launch_type = "FARGATE"
network_configuration {
subnets = var.private_subnet_ids
security_groups = [aws_security_group.ecs_tasks.id]
assign_public_ip = false
}
load_balancer {
target_group_arn = aws_lb_target_group.app.arn
container_name = "app"
container_port = 80
}
}
# Plus: security groups, IAM roles, ECS task execution role...
The CDK L3 construct creates the ECS cluster, task definition, service, ALB, target group, listener, security groups, and IAM roles, all with production-ready defaults. Terraform gives you more explicit control over each resource, but you're writing and maintaining that explicitness.
Terraform's advantage: You see exactly what's being created. No hidden resources, no magic defaults.
CDK advantage: L2/L3 constructs dramatically reduce boilerplate and bake in AWS best practices. When CDK Mixins adds a fourth composition pattern, you can apply compliance settings to L1 constructs without the full L2 rewrite.
For deep dives, see my posts on understanding CDK constructs and CDK stacks.
Learning Curve Reality Check
For developers: CDK is easier. You use familiar programming languages, existing tooling, and patterns you already know. Learning HCL is an extra cognitive load.
For ops teams: Terraform is easier. Declarative configuration is more approachable than learning to program. The mental model (describe what you want) is simpler than imperative code (describe how to get it).
For mixed teams: Terraform's HCL serves as a neutral middle ground that both developers and ops can work with.
CDK hidden complexity: You still need to understand CloudFormation behavior underneath. When debugging fails, you'll be reading CloudFormation error messages and understanding its state model.
Terraform hidden complexity: State management and HCL limitations take time to master. You'll learn the quirks of the state file the hard way.
Neither is "easy." They're different skill investments with different learning paths.
State Management Philosophy
State management is where these tools diverge most fundamentally.
CDK's CloudFormation-Managed State
CDK doesn't manage state directly; CloudFormation does it automatically.
When you deploy a CDK stack, CloudFormation tracks the state of all resources in that stack internally. There's no state file you manage or secure. CloudFormation operates under three modes per resource: Create, Update, Delete, and determines the current mode automatically based on the last deployment.
You can also run cdk drift to call CloudFormation's drift detection API, which compares actual resource state in AWS against expected configuration in CloudFormation. This is distinct from cdk diff, which compares your local CDK code against the deployed template. Both commands exist but serve different purposes: cdk diff is a pre-deployment check, cdk drift is a post-deployment health check.
Advantages:
- Zero state management overhead
- Automatic locking (no concurrent deployment conflicts)
- No security configuration needed for state storage
- No state file that could expose sensitive values
- State visible via CloudFormation console or API
Limitations:
- Tied to CloudFormation quotas and constraints (e.g., 500 resources per stack)
- Less control over state operations
- Can't easily inspect or manipulate state directly
Terraform's Manual State Configuration
Terraform stores state in JSON files (typically terraform.tfstate). By default this is local, but for team environments you configure a remote backend, almost always S3 for AWS users.
Updated recommended AWS setup (note: DynamoDB locking is now deprecated):
terraform {
backend "s3" {
bucket = "myorg-terraform-states"
key = "myapp/production/tfstate"
region = "us-east-1"
use_lockfile = true
}
}
The use_lockfile = true parameter uses native S3 state locking, available since Terraform 1.10.0. The old approach (using a DynamoDB table for locking via dynamodb_table) is deprecated and will be removed in a future Terraform version. AWS Prescriptive Guidance explicitly recommends migrating to S3 native locking if you're on the old pattern.
Advantages:
- Full control over state storage, versioning, and backup
- Works with any cloud provider
- Can inspect and manipulate state when needed
Challenges:
- Must configure encryption, access controls, and locking manually
- State file contains sensitive data (passwords, connection strings) in plain text, a real security risk if mishandled
- Team coordination around state requires discipline
Security bottom line: Terraform's state file exposure is higher risk. You need to be deliberate about state security: encrypt at rest with SSE-KMS, restrict IAM access to the state bucket, enable S3 versioning for rollback capability. CDK's automatic CloudFormation-managed state sidesteps this entire problem.
Setup and Prerequisites
What do you need before deploying anything?
CDK Bootstrap Requirements
CDK requires a one-time bootstrapping process per AWS account/region combination. As of March 2025, bootstrap templates v26 and v27 are current.
What bootstrapping creates (the CDKToolkit CloudFormation stack):
- S3 bucket for storing Lambda code and assets (naming:
cdk-hnb659fds-assets-ACCOUNT-REGION) - ECR repository for Docker images
- IAM roles:
CloudFormationExecutionRole,DeploymentActionRole,FilePublishingRole,ImagePublishingRole,LookupRole
Command: cdk bootstrap
Node.js requirement: Node.js 18.x support ended November 30, 2025. Node.js 22.x is now the recommended minimum.
The bootstrap stack is safe to run multiple times; it upgrades the template version if necessary. Re-bootstrapping is how you pick up new IAM role configurations or S3 bucket policies when AWS updates the bootstrap template.
For a detailed walkthrough, see my guide on AWS CDK bootstrapping.
If you want to skip the manual setup and start with a production-ready foundation, check out the AWS CDK Starter Kit. It includes secure OIDC authentication, automated CI/CD with GitHub Actions, and branch-based deployments: all the boilerplate I found myself copy-pasting across client projects, refined into a single starting point.
See the documentation for setup instructions.
Terraform Backend Setup
Terraform has no automatic bootstrapping; you configure the backend yourself.
Recommended setup for AWS:
- Create S3 bucket with versioning enabled
- Enable S3 server-side encryption (SSE-KMS)
- Create IAM policies restricting access
- Use
use_lockfile = truefor S3 native state locking (Terraform 1.10.0+)
Key difference: You must create these resources manually or with separate Terraform/CloudFormation before you can use them as your backend. It's a chicken-and-egg problem that teams typically solve with a small bootstrap script.
Comparison:
- CDK: One command, automatic resource creation, required per account/region
- Terraform: Manual resource setup, flexible configuration, per-project choice
If you want to skip the manual backend configuration and start with a production-ready setup, check out the AWS Terraform Starter Kit. It includes secure OIDC authentication, automated CI/CD with GitHub Actions, and security scanning built in.
See the documentation for setup instructions.
Deployment Workflow Comparison
What does your daily deployment experience look like?
CDK: Synth to Deploy to Rollback
The CDK deployment workflow:
- Synthesis:
cdk synthconverts your CDK code to CloudFormation templates (the "cloud assembly") - Preview:
cdk diffshows what will change compared to the deployed stack - Deploy:
cdk deploysynthesizes fresh templates, uploads assets to S3/ECR, creates/updates CloudFormation stack - Rollback: CloudFormation automatically rolls back on deployment failure
Asset handling:
- Lambda code is zipped and uploaded to the S3 staging bucket
- Docker images are built locally and pushed to the ECR repository
- CloudFormation references these assets during deployment
# Preview changes
cdk diff
# Deploy a specific stack
cdk deploy MyStack
# Deploy all stacks
cdk deploy "*"
Key benefit: Automatic rollback means failed deployments don't leave your infrastructure in a broken state.
Terraform: Init to Plan to Apply
The Terraform workflow is explicit about each step:
- Initialize:
terraform initdownloads providers and initializes the backend - Plan:
terraform planshows what changes will be made (compares state with desired config) - Apply:
terraform applyprovisions or updates infrastructure, updates state file - Destroy:
terraform destroyremoves all managed infrastructure
# Initialize (first time or when providers change)
terraform init
# Preview changes
terraform plan
# Apply changes
terraform apply
# Tear down everything
terraform destroy
No automatic rollback: If a deployment fails partway through, you must manually fix or destroy/recreate. This is a significant operational difference from CDK.
Previewing Changes: cdk diff vs terraform plan
Both tools let you preview changes before deployment; this is critical for production safety.
cdk diff:
- Compares current code with deployed CloudFormation stack
- Shows resource additions, modifications, deletions
- Displays property-level before/after values
terraform plan:
- Compares desired state (code) with current state (state file)
- Shows proposed changes with +/- indicators
- More detailed output showing attribute-level changes
Key difference: CDK diff queries the live CloudFormation stack; Terraform plan uses the state file. If someone modified infrastructure outside Terraform, the state file might be stale.
Always run preview before deploying to production. Make it a mandatory CI/CD gate.
Testing Your Infrastructure Code
Quality assurance for infrastructure is just as important as application code.
CDK's Built-in Testing Framework
CDK includes native testing support through the aws-cdk-lib/assertions module.
Two testing approaches:
Fine-grained assertions (recommended for most cases):
- Test specific resource properties
- Clear test failures indicating exact problems
- Stable across CDK version upgrades
import { Template } from 'aws-cdk-lib/assertions';
const template = Template.fromStack(stack);
// Verify Lambda function configuration
template.hasResourceProperties('AWS::Lambda::Function', {
Runtime: 'nodejs22.x',
Handler: 'index.handler',
MemorySize: 256
});
Snapshot tests:
- Compare entire synthesized template against a baseline
- Useful for catching unintended changes during refactoring
- Can break due to CDK upgrades or metadata changes
Compatible test frameworks: Jest (TypeScript/JavaScript), Pytest (Python), JUnit (Java), NUnit/xUnit (.NET)
Terraform's Third-Party Testing Ecosystem
Terraform has no native testing framework; you must use third-party tools.
Popular options:
- Terratest (Go-based): Deploys real infrastructure, runs tests, tears down
- terraform-compliance: Policy-as-code testing using BDD syntax
- tflint: Linter for Terraform code
- terraform validate: Built-in syntax validation (not comprehensive testing)
Trade-off: More flexible but requires additional tool learning and integration.
Testing complexity: Often requires actually deploying infrastructure to test (unlike CDK's template assertions). This makes tests slower and more expensive.
Which Approach Fits Your CI/CD Pipeline?
CDK advantage: Testing integrates with existing application test frameworks. Add infrastructure tests to the same pipeline as application code.
CDK workflow: Run npm test or pytest, fail the build on test failures, same as application code.
Terraform challenge: Requires separate testing tools and potentially separate CI stages.
Terraform benefit: Can test against actual deployed infrastructure (more realistic but slower and more expensive).
If your team already has a strong testing culture, CDK's built-in support reduces friction significantly.
Security and Compliance Comparison
Will your security team approve this tool, and how do you enforce compliance?
Security Scanning Tools
CDK security tools:
- cdk-nag: Checks CDK constructs against AWS best practices and compliance standards (HIPAA, PCI-DSS, etc.)
- CloudFormation Guard: Policy-as-code validation for synthesized templates
- CDK Aspects: Custom validation logic that runs during synthesis
Terraform security tools:
- tfsec: Static analysis for Terraform code
- Checkov: Multi-cloud policy-as-code scanner (works with both CDK and Terraform)
- Terraform Sentinel: HashiCorp's policy-as-code framework (requires Terraform Enterprise/Cloud)
- OPA (Open Policy Agent): Flexible policy engine
Terraform's security tooling ecosystem is more mature with more options.
Policy-as-Code Capabilities
CDK Aspects: TypeScript/Python code that validates constructs during synthesis.
Example use case: enforce S3 bucket encryption across all buckets. The Aspect runs before CloudFormation deployment and fails the build on violations.
CDK Mixins (new in March 2026): complement Aspects by allowing compliance settings to be applied directly at the construct level via .with(), which is less invasive than an Aspect traversal for specific resource configurations.
Terraform Sentinel/OPA: Policy languages for validating Terraform plans. Can prevent non-compliant infrastructure from being deployed.
Terraform's policy-as-code ecosystem is more established for enterprise compliance. If you need sophisticated policy enforcement with governance workflows, Terraform has more mature options.
Secret Management Approaches
Best practice for both tools: Use AWS Secrets Manager or Parameter Store, never hardcode secrets.
CDK approach:
// Reference existing secret at runtime
const secret = secretsmanager.Secret.fromSecretNameV2(
this, 'MySecret', 'my-secret-name'
);
Secrets are retrieved at runtime, not synthesis time. Never hardcode in CDK code.
Terraform approach:
data "aws_secretsmanager_secret_version" "my_secret" {
secret_id = "my-secret-name"
}
Critical: Secrets accessed via Terraform data sources can appear in the state file. You must protect the state file with encryption and access controls. AWS recommends rotating secrets immediately after Terraform ingestion.
Security concern: Terraform's state file exposure is higher risk than CDK for sensitive data.
Drift Detection and Remediation
CDK drift detection:
cdk drift: Calls CloudFormation's drift detection API to compare actual resource state against CloudFormation's expected configurationcdk diff: Compares your local CDK code against the deployed CloudFormation stack template (different fromcdk drift)- Available via console or API
- Shows which properties drifted and their actual vs expected values
- Cannot auto-remediate; must redeploy stack
Terraform drift detection:
terraform plancompares state file with actual infrastructure- Shows what would change to bring infrastructure back to desired state
terraform applyremediates drift- More integrated into normal workflow
Advantage Terraform: Drift detection is core to the plan/apply workflow, making it natural to catch and fix.
Honest Pros and Cons
Let's be direct about the real trade-offs you're accepting.
AWS CDK Advantages and Limitations
Advantages:
- Programming language support: TypeScript, Python, Java, C#, Go, JavaScript with full IDE features
- High-level abstractions: L2/L3 constructs reduce boilerplate dramatically (see the ECS Fargate example above)
- Automatic state management: CloudFormation handles state with zero configuration
- Built-in testing: Native unit and snapshot testing support
- AWS integration: First-class support, gets new features when CloudFormation does
- Best practices built-in: L2/L3 constructs encode AWS recommended configurations
- Rapid development: Create complex architectures with minimal code
- CloudFormation benefits: Automatic rollback, change sets, drift detection
- Reusable constructs: Share components via npm, PyPI, Maven, NuGet
- Type safety: Compile-time checking prevents configuration errors
- CDK Toolkit Library: Programmatic CI/CD integration without CLI subprocess calls
- CDK Mixins: Cross-cutting compliance features applied via
.with()on any construct - CDK Refactor: Safe code restructuring without triggering resource replacement
Limitations:
- Bootstrapping required: One-time setup per AWS account/region with multiple resources
- AWS-only: Cannot deploy to other cloud providers or SaaS platforms
- CloudFormation dependency: Limited by CloudFormation capabilities, quotas (500 resources/stack), and behaviors
- Learning curve for ops: Requires programming knowledge, steeper for traditional ops teams
- Provisioning speed: Deploys via CloudFormation, which is slower than Terraform's direct API approach
- Construct availability: Not all AWS features have L2/L3 constructs yet (fall back to L1/CFN)
Terraform Advantages and Limitations
Advantages:
- Multi-cloud support: 3,000+ providers for AWS, Azure, GCP, and diverse services
- Platform agnostic: No cloud vendor lock-in, consistent tooling across providers
- Mature ecosystem: Extensive modules, tools, and community (9+ years)
- No bootstrapping: No upfront environment setup required
- Declarative syntax: HCL easier for teams with diverse skillsets to learn
- Drift detection: Built-in drift detection core to the workflow
- Code reusability: Terraform Registry with thousands of vetted modules
- Broad adoption: Industry standard with extensive documentation and resources
- Agentless: No software installation on managed infrastructure
- Faster provisioning: Direct AWS API calls without CloudFormation intermediary
Limitations:
- Manual state management: Requires S3 bucket, encryption, IAM, and locking configuration
- State file security risk: Contains sensitive data in plain text requiring careful protection
- No automatic rollback: Manual intervention required for failed deployments
- Feature lag: New AWS features may take time to appear in AWS provider
- Limited testing: No native testing framework, requires third-party tools
- Licensing change: Business Source License instead of open source
- Business model risk: HashiCorp must monetize the tool itself, creating potential for future restrictive changes
- State locking migration: DynamoDB-based locking is deprecated; must migrate to S3 native locking (
use_lockfile = true) - HCL learning curve: New language to learn for developers used to general-purpose languages
Real-World Experiences
What problems will you actually face?
Common Gotchas and Pain Points
CDK Gotchas:
- CloudFormation stack updates can fail mid-deployment: manual intervention required via console
- Construct configuration sometimes synthesizes to unexpected CloudFormation: use
cdk synthto verify - Cross-stack references require stacks in same environment: common error for beginners
- Bootstrap version mismatches cause cryptic errors; keep bootstrap updated with
cdk bootstrap - L1 constructs require CloudFormation property knowledge: no abstraction benefit
- CDK Refactor constraint: if you need to add/delete/modify resources AND refactor simultaneously, you must split these into separate deployments
For help with cross-stack errors, see my guide on cross-stack references in CDK.
Terraform Gotchas:
- State file corruption from concurrent modifications: locking is critical
- Sensitive data in state file: must secure state bucket rigorously
- Provider version changes can break existing code; pin versions
- terraform plan doesn't catch all errors: some only appear during apply
- New AWS features lag: may wait weeks or months for provider updates
When the Pros Become Cons
CDK Examples:
- "Programming flexibility" becomes "too much magic" when debugging synthesized CloudFormation
- "High-level abstractions" become "I can't customize this" when L2/L3 constructs don't match your exact needs
- "Automatic state management" becomes "I can't debug state issues" when CloudFormation behaves unexpectedly
- "AWS-native integration" becomes "I'm locked into AWS" when business strategy shifts to multi-cloud
Terraform Examples:
- "Declarative simplicity" becomes "I can't express this logic" when you need complex conditional resource creation
- "Multi-cloud support" becomes "lowest common denominator" when AWS-specific features aren't available
- "Mature ecosystem" becomes "outdated modules" when community modules don't keep up with best practices
- "Manual state control" becomes "state file nightmare" when team grows and state conflicts increase
Decision Framework
Based on your specific situation, which tool should you choose?
The Role-Based Decision
Here's the organizing principle that most comparison posts bury in a footnote: who writes and maintains the infrastructure code daily?
The answer matters more than any feature comparison.
Software developers and application engineers: CDK is the natural fit. You use the language you already know, integrate infrastructure tests into your existing test suite, and leverage L2/L3 constructs to move fast on AWS. The programming model is familiar. The tooling is familiar.
DevOps/SRE and platform engineers: Terraform typically wins. Declarative configuration is more readable for infrastructure-focused roles. State management gives operational control that platform engineers are used to. The Terraform ecosystem maps to platform engineering workflows.
Mixed teams or teams with no clear IaC owner: Terraform's HCL is the neutral middle ground. HCL doesn't require programming expertise. CDK requires someone who can program, and that person has to be available when things break.
Engineering managers choosing for a team: ask "who will write and maintain this code daily?" before looking at any feature matrix. Training 10 ops engineers to program is a different investment than training 10 developers to learn HCL.
Choose CDK If
Definitive scenarios:
- Your infrastructure is 100% AWS with no multi-cloud requirements (now or in foreseeable future)
- Your team are developers who prefer TypeScript, Python, Java, C#, or Go over learning HCL
- You want to leverage L2/L3 constructs to reduce boilerplate and encode AWS best practices
- Testing is critical and you want native framework integration
- You're building AWS-native applications (serverless, containers) where CDK constructs shine
- You have governance requirements and want to distribute custom constructs with enforced standards
- You want CDK's fast-moving 2026 feature set (Mixins, Refactor, Toolkit Library)
Choose Terraform If
Definitive scenarios:
- You need multi-cloud or hybrid-cloud (AWS + Azure/GCP/on-premises)
- You manage non-AWS resources (GitHub, Datadog, Kubernetes, etc.) alongside AWS
- You want to avoid vendor lock-in as a strategic principle
- Your team are ops-focused and prefer declarative configuration over programming
- You have existing Terraform expertise and modules already built
- Drift detection is critical to your operational workflow
- Provisioning speed is a primary concern and you want direct API calls
Use Case Decision Matrix
| Use Case | CDK | Terraform | Pulumi | Why |
|---|---|---|---|---|
| Serverless applications | Best | Good | Good | CDK L3 constructs for Lambda, API Gateway reduce boilerplate significantly |
| Container orchestration (ECS/EKS) | Best | Good | Good | CDK patterns like ApplicationLoadBalancedFargateService simplify complex setups |
| Multi-account AWS Organizations | Good | Good | Good | Both work well; CDK if developers manage, Terraform if centralized ops team |
| Network infrastructure (VPC, Transit Gateway) | Good | Best | Good | Terraform's mature modules and declarative nature fit network configs well |
| Multi-cloud deployments | No | Best | Good | CDK doesn't support multi-cloud; Terraform for mature ecosystem, Pulumi for code |
| Data engineering pipelines | Good | Good | Good | CDK if Python-heavy team, Terraform if diverse data sources across clouds |
| Kubernetes infrastructure | No | Best | Good | Terraform has Kubernetes provider; CDK doesn't (use CDK8s for K8s resources) |
| Third-party SaaS (GitHub, Datadog) | No | Best | Good | Only Terraform/Pulumi support non-AWS providers |
| AWS-only with compliance requirements | Best | Good | No | CDK Aspects + Mixins provide compile-time policy enforcement |
For multi-account architectures, see my guide on AWS multi-account best practices.
Hybrid CDK + Terraform: When to Use Both
This section is for teams that have decided they need both tools. If you're still deciding, pick one; hybrid approaches add complexity that rarely pays off until you have a good reason.
The question for hybrid setups isn't whether to use both; it's where to draw the boundary.
The Layered Architecture Pattern
The pattern that works well in practice is clean vertical separation by resource lifespan and ownership:
Terraform manages the foundation layer: resources that change infrequently, serve multiple teams, and need to exist before any CDK stack can deploy:
- VPC and networking (subnets, route tables, NAT gateways)
- Shared IAM roles and permission boundaries
- Transit Gateway for multi-VPC routing
- Route 53 hosted zones
- Shared security groups
CDK manages the application layer: resources owned by a single application or service, deployed frequently by the team that owns the service:
- Lambda functions and API Gateway
- ECS services and task definitions
- Application-specific S3 buckets and queues
- DynamoDB tables for specific services
The boundary criterion: if a resource is shared across multiple CDK stacks or multiple teams, it belongs in Terraform. If a resource is owned by a single application or service, it belongs in CDK.
Sharing State Between CDK and Terraform
The standard integration pattern uses SSM Parameter Store as the bridge:
Terraform side: export outputs to SSM:
resource "aws_ssm_parameter" "vpc_id" {
name = "/platform/vpc/id"
type = "String"
value = aws_vpc.main.id
}
resource "aws_ssm_parameter" "private_subnet_ids" {
name = "/platform/vpc/private-subnet-ids"
type = "StringList"
value = join(",", aws_subnet.private[*].id)
}
CDK side: read at synthesis time:
import * as ssm from 'aws-cdk-lib/aws-ssm';
const vpcId = ssm.StringParameter.valueFromLookup(this, '/platform/vpc/id');
const subnetIds = ssm.StringParameter.valueFromLookup(this, '/platform/vpc/private-subnet-ids');
The CDK stack reads SSM parameters during cdk synth; if the Terraform foundation hasn't run yet, synthesis fails. That's intentional: it enforces the deployment order and makes the dependency explicit.
When Hybrid Adds More Complexity Than Value
Solo developers and small startups: don't do this. The overhead of two tool chains, two CI/CD patterns, and two sets of state management isn't worth it.
When hybrid makes sense:
- Large organizations with a platform team (Terraform) and application teams (CDK)
- Gradual migration from one tool to another
- Existing Terraform foundation that isn't worth rewriting, combined with CDK for new applications
- Multi-cloud foundation (Terraform) with AWS-native application layer (CDK)
Operational discipline required: two tool chains to install and maintain in CI/CD, two different PR review patterns, and a real risk of resource boundary overlap if teams aren't disciplined about the rules.
If the hybrid approach sounds like it fits, you'll want a migration plan. Here's how to move infrastructure between tools when your requirements change.
Migration Strategies
What if you choose wrong, or your requirements change?
Migrating from Terraform to CDK
The tool for this is cdk migrate, currently in preview (first documented February 2024). The important thing to know upfront: cdk migrate generates L1 constructs only. Getting to L2/L3 is manual work after migration.
Three migration sources:
# Migrate from a deployed CloudFormation stack
cdk migrate --from-stack --stack-name "myCloudFormationStack"
# Migrate from a local CloudFormation template
cdk migrate --from-path "./template.json" --stack-name "myCloudFormationStack"
# Migrate from deployed resources (uses IaC generator scan)
cdk migrate --from-scan --stack-name "myStack"
Key constraints you need to know:
- All migrated resources become L1 constructs; upgrading to L2/L3 is manual post-migration work
- Single stack per migration; nested templates are not supported
- Lambda code and project assets are not migrated automatically
- The Terraform-to-CDK path is indirect:
cdk migrateworks from CloudFormation stacks or raw AWS resources, not Terraform state directly
The Terraform-specific migration path:
- Identify Terraform-managed resources you want to migrate
- Use
cdk importto absorb individual resources into a CDK app (usecdk importfor a few resources into an existing app,cdk migratefor a whole stack into a new CDK app) - Verify logical IDs match between the CDK app and deployed resources
- Run
cdk diffto confirm no changes are detected - Refactor L1 constructs to L2/L3 where appropriate
- Remove Terraform configuration after CDK fully manages the resources
Stateful resources (databases, S3 buckets): verify the resource supports CloudFormation import before migrating. Deploy to the same account and region as the existing resource.
Migrating from CDK to Terraform
No automated migration tool: this must be done manually.
Migration strategy:
- Use
terraform importto bring existing CloudFormation-managed resources into Terraform state - Write Terraform configuration matching existing resources
- Run
terraform planto verify no changes detected - Test in non-production environment first
- Delete CDK stacks after Terraform manages resources
Challenges:
- Must write Terraform config from scratch
- CloudFormation-specific resource configurations may not translate directly
- Import process is resource-by-resource (tedious for large infrastructures)
Alternative: Recreate infrastructure with Terraform (blue/green migration).
This asymmetry matters: migrating Terraform to CDK has a tool (cdk migrate); migrating CDK to Terraform is entirely manual. That's one concrete reason to think carefully before starting with Terraform if you expect to want CDK later.
Risk Mitigation for Lock-In Concerns
Both tools create lock-in: switching is non-trivial in either direction.
Mitigation strategies:
- Keep infrastructure simple and modular (easier to rewrite)
- Document architectural decisions
- Invest in testing to enable confident refactoring
- Consider gradual migration over big-bang approach
- Maintain infrastructure diagrams independent of IaC tool
CDK lock-in is twofold: tool lock-in (CDK itself) + vendor lock-in (AWS via CloudFormation).
Terraform lock-in: tool lock-in but platform flexibility via providers.
If you're genuinely worried about lock-in, Terraform's multi-cloud support provides more architectural flexibility by design.
How CDK and Terraform Fit in the IaC Landscape
What about CloudFormation, SAM, Pulumi, and other IaC tools?
Quick Comparison with CloudFormation, SAM, and Pulumi
AWS Prescriptive Guidance provides a useful framework for choosing between IaC tools. Here's the decision matrix:
| Scenario | Recommended Tool |
|---|---|
| Serverless AWS with minimal dependencies | AWS SAM |
| Infrastructure entirely on AWS, state management important | AWS CloudFormation or AWS CDK |
| Multi-cloud or hybrid-cloud, multi-provider | Terraform |
| Top-down construct reuse, governance, compliance distribution | AWS CDK |
| Multi-cloud with high risk tolerance | Pulumi |
| Multi-cloud with existing Terraform investment | Terraform |
The full tool comparison:
| Tool | Best For | Language | Scope | Relationship |
|---|---|---|---|---|
| CloudFormation | AWS-native declarative IaC | YAML/JSON | AWS only | CDK synthesizes to CloudFormation |
| AWS SAM | Serverless applications | YAML (simplified) | Serverless-focused | Subset of CloudFormation |
| AWS CDK | AWS infrastructure with programming | TypeScript, Python, Java, C#, Go, JS | AWS only | Layer on top of CloudFormation |
| Terraform | Multi-cloud infrastructure | HCL | Multi-cloud | Alternative deployment engine |
| Pulumi | Multi-cloud with programming | TypeScript, Python, Go, C#, Java | Multi-cloud | CDK-like experience for non-AWS clouds |
Key differentiators:
- CloudFormation = AWS-native, declarative, YAML/JSON (CDK's underlying deployment engine)
- CDK = CloudFormation with programming languages and high-level abstractions
- Terraform = multi-cloud, HCL, mature ecosystem, direct API calls
- Pulumi = multi-cloud with programming languages (CDKTF's spiritual successor, actively maintained)
When to consider alternatives:
- CloudFormation directly: If you prefer YAML/JSON and don't need programming abstractions
- AWS SAM: If you're building purely serverless applications and want simplified syntax
- Pulumi: If you want CDK-like programming experience with multi-cloud support
For a deeper look at AWS-native IaC options, see my comparison of Control Tower alternatives from console to code.
Conclusion: Making Your Decision with Confidence
Let me give you the specific, non-vague version of "here's what to do."
Key Takeaways
-
AWS CDK wins for AWS-native teams: programming languages, automatic CloudFormation-managed state, rich L2/L3 abstractions, and a fast-moving 2025-2026 feature set (Toolkit Library, Refactor, Mixins)
-
Terraform wins for multi-cloud and platform teams: declarative HCL, mature ecosystem, direct API calls (faster provisioning), but manual state management and BSL licensing
-
CDKTF is dead: don't plan around it. If you were on CDKTF for programming languages + AWS, migrate to CDK. If you needed multi-cloud, evaluate Pulumi.
-
CDK deploys via CloudFormation: roughly 2-3x slower than Terraform for the same resources;
cdk watchnarrows the gap in development workflows -
Hybrid is valid with clear boundaries: Terraform for foundation (VPC, shared IAM, networking), CDK for application layer, SSM Parameter Store as the state bridge
Your Next Action Step
The best way to validate this decision for your specific team is to build the same infrastructure (VPC + Lambda + S3) in both tools in a scratch AWS account, time it, and see which development experience feels natural. Commit to one tool within two weeks; indecision is more costly than picking either.
If you're choosing CDK:
- Install AWS CDK:
npm install -g aws-cdk - Read my comprehensive beginner's guide to AWS CDK
- Review CDK best practices for production-ready patterns
- Learn about bootstrapping: AWS CDK Bootstrap: The Why and the How
- Explore project structure: Optimize your AWS CDK Project Structure for Growth
If you're choosing Terraform:
- Install Terraform: follow HashiCorp's installation guide
- Read AWS Prescriptive Guidance for Terraform
- Set up S3 backend with
use_lockfile = true(not DynamoDB; that's deprecated) - Explore Terraform Registry for AWS modules
- Review Terraform AWS Provider Best Practices
If you're still uncertain: build the same simple infrastructure in both tools, evaluate which feels more natural for your team, and make a decision. Both are good tools; the right one depends on your context.
What's your biggest hesitation in making this choice? Drop a comment below and let's discuss.
Need Help With Your AWS CDK Implementation?
I review your CDK infrastructure code for best practices, security compliance, and maintainability. Get actionable recommendations to improve your Infrastructure as Code quality and reduce technical debt.

![AWS CDK Project Structure: The Complete Guide to Organizing Your CDK App [2026]](/_next/image?url=%2Fimages%2Fblog%2Faws-cdk-project-structure%2Faws-cdk-project-structure.jpg&w=3840&q=70)