AWS CDK and Terraform are the two dominant infrastructure-as-code tools for AWS. They can both provision any AWS resource, both integrate with CI/CD pipelines, and both have large ecosystems. The choice between them is genuinely consequential — migrating IaC tooling mid-project is expensive.
Here’s how the tools actually differ and when each is the right choice.
The fundamental model difference
Terraform uses HCL (HashiCorp Configuration Language) — a declarative configuration language. You describe the desired end state of your infrastructure. Terraform figures out how to get there. The state file tracks what Terraform has provisioned; terraform plan shows the diff between current state and desired state; terraform apply makes it real.
AWS CDK uses general-purpose programming languages (TypeScript, Python, Java, Go, .NET) to define infrastructure. You write code that constructs AWS CloudFormation templates, which are then deployed. CDK is ultimately a CloudFormation generator — the actual provisioning is done by CloudFormation.
This distinction matters for several practical reasons.
What you’re actually deploying
Terraform: Terraform calls AWS APIs directly via the AWS provider. It maintains its own state of what exists. No CloudFormation involvement.
CDK: CDK synthesizes your code into a CloudFormation template, then deploys that template. All CDK deployments create CloudFormation stacks. Rollback behavior, deployment strategies, and resource change sets are CloudFormation’s.
Implication: CDK is subject to CloudFormation limits and behaviors — 500 resources per stack (workaround: nested stacks), CloudFormation deployment timeouts, and CloudFormation’s resource type coverage. If a CloudFormation resource type doesn’t exist or has a bug, CDK can’t work around it (without CloudFormation custom resources).
Terraform is not subject to CloudFormation limits and can use custom providers for any API, not just AWS.
Developer experience
CDK advantages:
- Real programming language — loops, conditionals, functions, classes, abstractions
- Type safety (especially in TypeScript) catches misconfigurations before deploy
- IDE autocomplete for resource properties
- Constructs ecosystem: reusable patterns (L2/L3 constructs) that encapsulate best-practice configurations
- Testing with Jest, pytest, or equivalent — unit test your infrastructure logic
Terraform advantages:
- HCL is approachable for non-developers (ops teams, infrastructure engineers without software engineering background)
- Explicit state model is easier to reason about
terraform planoutput is unambiguous about what will change- Mature ecosystem of modules in the Terraform Registry
- Works for multi-cloud (Azure, GCP, Kubernetes, third-party providers) — single tool across your entire infrastructure
The CDK construct levels
CDK has three levels of constructs:
L1 (CloudFormation resources): Direct wrappers around CloudFormation resource types. Every property maps 1:1 to CloudFormation. Full control, no opinions.
// L1 — verbose, full control
const bucket = new s3.CfnBucket(this, 'MyBucket', {
bucketEncryption: {
serverSideEncryptionConfiguration: [{
serverSideEncryptionByDefault: {
sseAlgorithm: 'AES256'
}
}]
},
publicAccessBlockConfiguration: {
blockPublicAcls: true,
blockPublicPolicy: true,
ignorePublicAcls: true,
restrictPublicBuckets: true
}
});
L2 (curated constructs): Higher-level abstractions with sensible defaults and helper methods. Most teams use L2 constructs for common resources.
// L2 — sensible defaults applied automatically
const bucket = new s3.Bucket(this, 'MyBucket', {
encryption: s3.BucketEncryption.S3_MANAGED,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
versioned: true,
removalPolicy: cdk.RemovalPolicy.RETAIN
});
L3 (patterns): Complete solutions combining multiple resources. An L3 construct might create an ALB, target group, ECS service, and all associated IAM roles in one call.
The L2 and L3 constructs enforce AWS best practices by default — encryption, public access blocking, IAM least-privilege — without you specifying every detail.
State management comparison
Terraform state is managed explicitly:
- Stored in S3 (with DynamoDB lock table for team use)
- Visible as JSON — you can read it, manipulate it, import existing resources
terraform state mv,terraform import,terraform state rmfor state surgery- Drift detection via
terraform planagainst real infrastructure
CDK state is CloudFormation stack state:
- Managed by AWS (you don’t manage a state file)
- No equivalent to
terraform import— importing existing resources into CDK is awkward - Drift detection via CloudFormation drift detection (less granular than Terraform)
- Stack deletion removes all resources (unless
RemovalPolicy.RETAINset)
For brownfield environments (importing existing infrastructure): Terraform’s explicit import is significantly better than CDK’s. If you’re taking over an existing AWS environment and want IaC coverage, Terraform lets you import resources one at a time. CDK requires you to either use CDK migration tooling (limited) or recreate resources (risky).
Multi-account and multi-region
CDK with cdk-pipelines: AWS provides a CDK pipelines construct for multi-account, multi-region deployments. Stacks can be targeted to specific account/region combinations. The deployment pipeline (CodePipeline-backed) handles cross-account assumptions.
const pipeline = new pipelines.CodePipeline(this, 'Pipeline', {
synth: new pipelines.ShellStep('Synth', {
input: pipelines.CodePipelineSource.gitHub('org/repo', 'main'),
commands: ['npm install', 'npm run build', 'npx cdk synth']
})
});
pipeline.addStage(new MyAppStage(this, 'Prod', {
env: { account: '123456789', region: 'us-east-1' }
}));
Terraform with workspaces or Terragrunt: Terraform workspaces separate state by environment. Terragrunt (popular wrapper) handles multi-account patterns with DRY configuration. Both approaches work at scale but require more configuration than CDK’s native multi-account support.
When to choose CDK
Your team has software engineering background and thinks in code, not configuration files. TypeScript CDK with L2 constructs lets them apply software engineering practices to infrastructure — testing, abstraction, code review.
You’re 100% AWS. CDK is AWS-only. If your infrastructure is exclusively AWS with no plans for other clouds, CDK’s deep AWS integration is a genuine advantage.
You want enforced best practices. L2 constructs apply AWS-recommended defaults. A team that might forget to enable bucket encryption or public access blocking gets those defaults for free.
You’re building complex, reusable infrastructure patterns. L3 constructs and custom constructs let you build internal platform abstractions — an ApplicationStack that enforces your company’s standard logging, monitoring, and networking configuration. Harder to achieve cleanly in HCL.
When to choose Terraform
Your team manages multi-cloud infrastructure. One tool, one state model, one CLI across AWS, Azure, GCP, and third-party providers.
Your team includes non-developers. HCL is more approachable for infrastructure engineers who don’t write software daily. CDK in TypeScript requires software development knowledge.
You’re working with existing (brownfield) infrastructure. terraform import is mature and handles resource import cleanly.
You want explicit, readable infrastructure definitions. HCL terraform plan output is unambiguous. For teams that need infrastructure definitions that non-experts can read and audit, HCL is clearer than CDK’s synthesized CloudFormation.
You use non-AWS-native tooling. Terraform providers exist for GitHub, Datadog, PagerDuty, Snowflake, and hundreds of other services. You can manage your entire infrastructure footprint — not just AWS — in one tool.
The practical reality
Both tools work. Large, sophisticated AWS environments run on both. The choice is less about technical superiority and more about team composition, existing tooling, and future cloud strategy.
The worst outcome is a team that picks one, uses it for a year, then switches — because migrating IaC tooling means reprovisioning or manually editing state, neither of which is trivial at scale.
If you’re starting fresh on AWS with a software engineering team and staying AWS-only: CDK with TypeScript is a strong choice. If you’re multi-cloud, have a mixed team, or need to import existing infrastructure: Terraform.
If you’re evaluating IaC tooling for a new platform or considering a migration, I’m available to help.
Nick Allevato is an AWS Certified Solutions Architect Professional with 20 years of infrastructure experience. He runs Cold Smoke Consulting, an independent AWS consulting practice.