Terraform vs Pulumi vs CDK.
HCL vs TypeScript vs L2-constructs. Three good tools, three different bets on what infrastructure code should look like — and one wrong pick will haunt a 5-engineer platform team for years. Here's how we'd choose.
Every IaC choice is downstream of one philosophy fight: should infrastructure be configuration (declarative DSL) or programming (general-purpose language)? Terraform's answer is configuration with escape hatches. Pulumi's answer is "it's just code." CDK's answer is "configuration generated from code, then handed off to CloudFormation."
All three work. The differences show up at the 18-month mark, when state is sprawling, drift is real, and the second engineer joins the platform team.
Terraform / OpenTofu: The Default
Philosophy: declarative HCL, vendor-neutral providers, state as source of truth.
Terraform won the IaC race by being good-enough in 2017 and never losing momentum. The ~3,000-provider ecosystem covers anything you'd touch. State management (S3 + DynamoDB lock or Terraform Cloud) is well-understood. HCL's grammar is intentionally limited — you can't shoot yourself in the foot with arbitrary code, which is a feature in a 20-engineer team.
Where it bites: HCL is not a programming language. Once you need branching logic, recursion, or composition beyond modules, you reach for `for_each` and `dynamic` blocks and start writing string-templated JSON. The Terragrunt + Terraspace + Atmos ecosystem exists exactly because HCL maxes out at "moderately complex." HashiCorp's BSL license drama in 2023 spun out OpenTofu (Apache 2.0 fork) — at this point, OpenTofu is the safer default for new teams. Migration off Terraform is straightforward.
Pick Terraform/OpenTofu when: you're multi-cloud or vendor-neutral, your platform team is 2-10 engineers, you want a battle-tested ecosystem, you don't have polyglot infra needs.
Pulumi: Infrastructure as Real Code
Philosophy: write infrastructure in TypeScript / Python / Go / C# / Java; share libraries; test with real frameworks.
Pulumi's bet is that "configuration languages always become bad programming languages eventually." If infra needs branching and composition, why not start with a real language? The result is excellent for teams whose engineers are already TypeScript-fluent and want to share `tsconfig` discipline, unit tests, and dependency management with their app code.
Where it bites: you can shoot yourself in many feet. The same `if/else` that makes Pulumi expressive also lets a junior engineer write `if env === 'prod' && date.getDay() === Math.random()` and ship a config bomb. State management is similar to Terraform's (Pulumi Service or self-hosted). The ecosystem is smaller (~150 providers vs ~3000), and several Terraform providers don't have Pulumi-native equivalents — though `tf2pulumi` covers many gaps.
Pick Pulumi when: your engineering culture is "everything is code," you have polyglot infra (e.g. Kubernetes + AWS + Vault), the platform team is mature (≥5 engineers), and code-review discipline is solid.
AWS CDK: The Constructs-Generator
Philosophy: write code (TypeScript / Python / Java / C#) that generates CloudFormation, deployed by CloudFormation.
CDK is AWS's answer for AWS-first shops who want Pulumi-style ergonomics without leaving the CloudFormation guarantees (rollback, drift detection, change sets). The L1 constructs are 1:1 with CFN resources; L2 adds sensible defaults; L3 (patterns) ships entire architectures as objects. The `aws-cdk-lib` ecosystem is excellent for AWS-only deployments.
Where it bites: you're locked to CloudFormation. That's a feature if you love CFN, a bug if you don't (rollback semantics, slow change sets, stack limits). Multi-cloud is a non-starter — you'd need CDKTF (CDK for Terraform) which loses half the L2 ergonomics. AWS-CDK feature lag behind raw AWS APIs is real; you'll hit "the L1 supports it, the L2 doesn't yet" friction monthly. Plus drift detection is via CFN's mechanism, which is incomplete vs Terraform's plan-diff.
Pick CDK when: you're AWS-only, your team prefers TypeScript over HCL, you want CFN's rollback semantics, and you don't anticipate going multi-cloud in 3-5 years.
Decision matrix
| Capability | Terraform/OpenTofu | Pulumi | AWS CDK |
|---|---|---|---|
| Multi-cloud | ✅ best | ✅ good | ❌ AWS-only (or CDKTF compromise) |
| Provider ecosystem | ✅ ~3,000 | ⚠ ~150 + tf2pulumi gap | ✅ AWS-native (L2 lag) |
| Programming language ergonomics | ❌ HCL only | ✅ TypeScript/Python/Go/C#/Java | ✅ TS/Python/Java/C# |
| State management | ✅ S3+lock or TFC | ✅ Pulumi Service or self-host | ⚠ via CFN (limited) |
| Drift detection | ✅ plan diff | ✅ preview diff | ⚠ CFN drift (incomplete) |
| Composition / testing | ⚠ modules + Terratest | ✅ real package mgmt + jest/pytest | ✅ npm + jest |
| Junior-engineer safety | ✅ HCL grammar floor | ⚠ full language footguns | ⚠ full language footguns |
| Hiring pool | ✅ largest | ⚠ smaller | ⚠ AWS-CDK specialists |
How we'd decide
The 80% answer: start with OpenTofu. It's the lowest-risk default for any team under 50 engineers. The HCL grammar floor protects you from the foot-gun class, the ecosystem covers everything, and migration paths exist in every direction.
Switch to Pulumi when your team is mature, your infra is polyglot (Kubernetes operators + AWS + custom stuff), and you've felt the HCL ceiling — usually at the 18-24 month mark.
Pick CDK only when you're AWS-only AND organizationally committed to CFN's rollback model AND have AWS-CDK specialists already on staff. The lock-in is real and 3-year migrations are common when teams change their mind.
The mistake we see most: teams pick on Twitter buzz (Pulumi) when their team is 3 generalists and a junior — then write infrastructure code that becomes unmaintainable in 6 months. Or pick CDK because "we're an AWS shop today" without thinking about Cloudflare or GCP showing up in 2 years.
IaC posture on retainer.
Audit Retainer reviews your infrastructure code monthly — module quality, state architecture, drift detection, security gates, migration risk. PRs merged against your repos with the fixes already implemented, not findings filed in a deck.