Every AWS team eventually asks: "Should we use Terraform, CDK, or CloudFormation?" The answer isn't universal — it depends on your team's skills, organizational constraints, multi-cloud requirements, and how you want to manage infrastructure lifecycle.
This guide compares all three across the dimensions that actually matter in production, with a decision framework to help you choose.
The Three Models at a Glance
┌───────────────────────────────────────────────────────────────────┐
│ INFRASTRUCTURE AS CODE │
├───────────────────┬───────────────────┬───────────────────────────┤
│ CloudFormation │ AWS CDK │ Terraform │
│ │ │ │
│ Declarative │ Imperative │ Declarative │
│ JSON/YAML │ TypeScript/ │ HCL │
│ │ Python/Java/Go │ │
│ AWS-native │ Synthesizes to │ Multi-cloud │
│ │ CloudFormation │ (4000+ providers) │
│ No state file │ No state file │ State file required │
│ (AWS manages) │ (AWS manages) │ (you manage) │
└───────────────────┴───────────────────┴───────────────────────────┘
CloudFormation: The AWS Native
What it is: AWS's own IaC service. Declarative JSON/YAML templates that describe desired state. AWS handles provisioning, ordering, and rollback.
Strengths
- Zero setup — no tools to install, no state to manage
- Same-day AWS support — new services/features available immediately in CloudFormation
- Drift detection — detects when resources deviate from template
- Stack operations — create, update, delete as atomic operations with automatic rollback
- StackSets — deploy across multiple accounts and regions from one template
- Change sets — preview changes before applying
- No state file — AWS tracks resource state internally (no S3 backend, no locking concerns)
Weaknesses
- Verbose — simple resources require many lines of YAML/JSON
-
No loops or conditionals (limited) —
ConditionsandFn::ForEachare awkward - No abstraction — can't create reusable "classes" of infrastructure
- AWS-only — cannot manage non-AWS resources
- Slow updates — large stacks take 30+ minutes to update
- Error messages — often cryptic, debugging is painful
Best For
- Teams deeply committed to AWS with no multi-cloud plans
- Organizations using AWS Service Catalog (backed by CloudFormation)
- Landing Zone Accelerator (LZA) deployments
- Simple, single-account deployments with < 50 resources
AWS CDK: Infrastructure in Real Code
What it is: An open-source framework that lets you define infrastructure using programming languages (TypeScript, Python, Java, Go, C#). CDK synthesizes to CloudFormation — it's an abstraction layer on top.
Strengths
- Real programming languages — loops, conditionals, functions, classes, inheritance
- Constructs — reusable, composable building blocks (L1, L2, L3)
- Type safety — IDE autocomplete, compile-time checks, refactoring support
- Abstraction — define a "SecureWebApp" construct once, reuse everywhere
- Testing — unit test infrastructure with standard testing frameworks (Jest, pytest)
- Same deployment model as CloudFormation — benefits from AWS-managed state, rollback, drift detection
- Construct Hub — community library of pre-built patterns (aws-solutions-constructs)
Weaknesses
- Still limited by CloudFormation — if CFN can't do it, CDK can't either
- Synthesis step — adds complexity to CI/CD pipelines
- Learning curve — must know both the programming language AND AWS resource model
- Stack size limits — inherits CloudFormation's 500-resource limit per stack
- Breaking changes — CDK library updates can break existing constructs (semver issues)
- AWS-only — synthesizes to CloudFormation, so no multi-cloud
CDK Construct Levels
| Level | Description | Example |
|---|---|---|
| L1 | Direct CloudFormation mapping (CfnBucket) | 1:1 with CFN, verbose |
| L2 | AWS-aware with sensible defaults (Bucket) | Encryption enabled by default, fewer params |
| L3 | Opinionated patterns (ApplicationLoadBalancedFargateService) | Full architecture in one construct |
Best For
- Developer-heavy teams familiar with TypeScript/Python
- Organizations wanting reusable infrastructure libraries (shared constructs)
- Complex deployments needing loops, conditionals, and dynamic generation
- Teams already using CloudFormation wanting better developer experience
- Projects requiring infrastructure unit testing
Terraform: The Multi-Cloud Standard
What it is: HashiCorp's open-source IaC tool using HCL (HashiCorp Configuration Language). Declarative, provider-based architecture supporting 4,000+ providers across all major clouds and SaaS services.
Strengths
- Multi-cloud — same workflow for AWS, Azure, GCP, Kubernetes, Datadog, PagerDuty, GitHub, etc.
- Mature ecosystem — Terraform Registry with 15,000+ modules
- HCL is purpose-built — cleaner than YAML, simpler than full programming languages
-
Plan before apply —
terraform planshows exactly what will change - Module system — reusable, versioned, composable modules
- Import existing resources — bring manually-created resources under management
- Fast execution — parallel resource creation, faster than CloudFormation for large deployments
- Community — massive community, extensive documentation, Stack Overflow coverage
Weaknesses
- State management — YOU manage the state file (S3 + DynamoDB locking is standard)
- State drift — if someone changes resources outside Terraform, state diverges
- No built-in rollback — failed applies can leave infrastructure in partial state
- Provider lag — new AWS features may take days/weeks to appear in the AWS provider
- License change — BSL license since 2023 (OpenTofu is the open-source fork)
- HCL limitations — no full programming language features (workarounds needed for complex logic)
- Sensitive data in state — state file contains secrets (must encrypt S3 backend)
Terraform vs OpenTofu
Since HashiCorp's BSL license change in 2023, OpenTofu exists as a fully open-source fork:
| Criteria | Terraform | OpenTofu |
|---|---|---|
| License | BSL (Business Source License) | MPL 2.0 (truly open source) |
| Feature parity | Leading edge | Follows (slight lag) |
| Provider support | Full | Full (same providers) |
| Enterprise support | Terraform Cloud/Enterprise | Community + vendors |
| When to choose | Need Terraform Cloud features | Need fully open-source |
Best For
- Multi-cloud or hybrid environments
- Platform teams managing infrastructure across multiple providers
- Organizations with existing Terraform expertise
- Projects needing to manage non-AWS resources (GitHub repos, DNS, monitoring, SaaS configs)
- Large-scale deployments where CloudFormation is too slow
Head-to-Head Comparison
| Criteria | CloudFormation | CDK | Terraform |
|---|---|---|---|
| Language | JSON/YAML | TypeScript/Python/Java/Go | HCL |
| Paradigm | Declarative | Imperative (synthesizes to declarative) | Declarative |
| Multi-cloud | ❌ AWS only | ❌ AWS only | ✅ 4000+ providers |
| State management | AWS-managed | AWS-managed (via CFN) | Self-managed (S3 + DynamoDB) |
| Rollback | ✅ Automatic | ✅ Automatic (via CFN) | ❌ Manual |
| New AWS features | Same-day | Same-day (via L1) | Days/weeks lag |
| Reusability | Nested stacks (limited) | Constructs (excellent) | Modules (excellent) |
| Testing | ❌ No native support | ✅ Unit + integration | ✅ Terratest, plan assertions |
| Learning curve | Low (if you know YAML) | Medium (language + AWS) | Low-Medium (HCL) |
| IDE support | Basic YAML validation | Full (autocomplete, types) | Good (HCL extension) |
| Drift detection | ✅ Built-in | ✅ Built-in (via CFN) |
terraform plan (manual) |
| Import existing | ✅ (limited) | ✅ (limited) | ✅ (excellent) |
| Community modules | Limited | Construct Hub (growing) | Registry (massive, 15K+) |
| CI/CD integration | AWS-native (CodePipeline) | CDK Pipelines | Any CI tool |
| Cost | Free | Free | Free (OSS) / Paid (Cloud) |
State Management: The Critical Difference
CloudFormation / CDK: No State File Worries
AWS tracks state internally. You never see a state file. Benefits:
- No S3 backend to configure
- No locking mechanism needed
- No state corruption risk
- Drift detection built-in
Terraform: You Own the State
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/network/terraform.tfstate"
region = "eu-west-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
State risks you must manage:
- State file contains secrets (encrypt the S3 bucket, restrict access)
- Concurrent applies can corrupt state (DynamoDB locking solves this)
- Lost state = Terraform doesn't know what it manages (backup state files)
- State drift = someone changed resources outside Terraform (regular
terraform plandetects this)
Module / Construct Ecosystem
Terraform Registry
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.0.0"
cidr = "10.0.0.0/16"
azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}
15,000+ community modules. The terraform-aws-modules organization alone covers VPC, EKS, RDS, Lambda, and dozens more.
CDK Constructs
import { ApplicationLoadBalancedFargateService } from 'aws-cdk-lib/aws-ecs-patterns';
new ApplicationLoadBalancedFargateService(this, 'MyApp', {
taskImageOptions: { image: ecs.ContainerImage.fromRegistry('my-app:latest') },
desiredCount: 3,
cpu: 512,
memoryLimitMiB: 1024,
});
// This single construct creates: ECS cluster, Fargate service, ALB, target group,
// security groups, IAM roles, CloudWatch log group — all with sensible defaults
Team Workflow Patterns
Pattern 1: Terraform for Platform, CDK for Applications
Platform Team → Terraform
├── VPCs, Transit Gateway, DNS
├── EKS clusters, RDS instances
└── Shared infrastructure (S3, KMS, IAM boundaries)
Application Teams → CDK
├── ECS services, Lambda functions
├── Application-specific resources
└── Deploy via CDK Pipelines
Why: Platform team needs multi-provider support (AWS + GitHub + Datadog). App teams benefit from CDK's developer-friendly constructs.
Pattern 2: Terraform Everywhere
All Teams → Terraform
├── Shared modules in internal registry
├── Atlantis or Terraform Cloud for PR-based workflow
└── One language, one workflow, one state backend
Why: Consistency. One tool to learn, one CI/CD pattern, one troubleshooting approach.
Pattern 3: CDK Everywhere
All Teams → CDK (TypeScript)
├── Shared construct library (internal npm package)
├── CDK Pipelines for deployment
└── Jest for infrastructure testing
Why: Developer-first organization where infrastructure is code written by application developers.
The Decision Flowchart
START
│
├── Do you need to manage non-AWS resources?
│ └── YES → Terraform (multi-provider)
│
├── Is your team primarily developers (TypeScript/Python)?
│ ├── YES → CDK (familiar language, constructs, testing)
│ └── NO → Continue ↓
│
├── Do you need multi-cloud portability?
│ └── YES → Terraform
│
├── Is simplicity the priority (small team, few resources)?
│ └── YES → CloudFormation (no tools to manage)
│
├── Do you need reusable infrastructure libraries?
│ ├── Developer org → CDK (constructs)
│ └── Ops/platform org → Terraform (modules)
│
└── Already using one tool successfully?
└── YES → Stay with it (switching cost > marginal benefit)
Migration Considerations
| From | To | Effort | When It Makes Sense |
|---|---|---|---|
| CloudFormation → CDK | Low | CDK can import existing CFN stacks. Migrate incrementally. | |
| CloudFormation → Terraform | Medium | Use terraform import. Re-create templates in HCL. |
|
| Terraform → CDK | High | No migration path. Must re-create and import. | |
| CDK → Terraform | High | No migration path. Must re-create and import. |
Key rule: Don't migrate for marginal gains. Only migrate when the current tool is actively blocking you (e.g., multi-cloud requirement, team can't hire CloudFormation skills).
What About Pulumi / CDKTF / Crossplane?
| Tool | Niche |
|---|---|
| Pulumi | Like CDK but multi-cloud. Real languages + any cloud provider. Consider if you want CDK-style + multi-cloud. |
| CDKTF (CDK for Terraform) | Write CDK-style code that synthesizes to Terraform HCL. Best of both worlds — but adds complexity. |
| Crossplane | Kubernetes-native IaC. Manages cloud resources via K8s CRDs. For teams running everything on Kubernetes. |
Summary
| Choose... | When... |
|---|---|
| CloudFormation | AWS-only, simple deployments, no state management overhead, same-day feature support |
| CDK | Developer teams, need reusable constructs, want type safety and testing, AWS-only is fine |
| Terraform | Multi-cloud, platform teams, large module ecosystem, existing HCL expertise, non-AWS resources |
The "wrong" choice isn't which tool you pick — it's switching tools every 6 months because someone read a blog post. Pick one, standardize, and build expertise. The best IaC tool is the one your team uses consistently.
Alpesh Kumbhare is an AWS Architect at Atos, specializing in AWS infrastructure automation and IaC best practices. Connect on LinkedIn.
Top comments (0)