Yashveer Singh
Connect
<- All posts

Infrastructure as Code: Terraform vs Pulumi vs CDK

Infrastructure as code is the practice of defining cloud resources in version-controlled files rather than through manual console clicks. Terraform, Pulumi, and AWS CDK are the three dominant tools for doing this, each with a different philosophy: Terraform uses its own declarative language, Pulumi uses general-purpose programming languages, and CDK uses TypeScript or Python to generate CloudFormation.

Written by Yashveer Singh, founder of Yashveer Labs.

What you need to know

  • Terraform is the default choice for teams that need multi-cloud coverage, a large provider ecosystem, and broad community support. HCL is a tradeoff: simpler for infrastructure descriptions, but limited for complex logic.
  • Pulumi is the right choice when your team finds HCL limiting and wants to write real programming logic against cloud APIs. It supports TypeScript, Python, Go, and others.
  • AWS CDK is a strong choice for AWS-only shops that want to write TypeScript or Python and leverage the L2 construct library, which provides sensible defaults for complex resources like ECS services and RDS clusters.
  • State management is the operational overhead that all three tools require. The tooling for managing and protecting state differs and should be evaluated as part of the adoption decision.
  • The switching cost between these tools is significant. Choose based on your team's existing strengths and long-term cloud strategy, not hype cycles.

The core argument

The IaC decision is really a language decision wrapped in an infrastructure question. All three tools can provision the resources you need. The practical differences are in how you express your intent, how readable the result is for a team member who did not write it, and how well the tool handles the complexity of your environment six months from now when the infrastructure has grown substantially.

Terraform's HCL is purpose-built for infrastructure descriptions. It is readable, declarative, and forces a clean separation between resource definitions and logic. The limitation is that HCL is not a general-purpose language, so when you need conditional logic, dynamic resource counts, or shared abstractions, you end up in module and workspace patterns that become complex. The state management workflow is also its own operational responsibility: you need a remote backend, locking, and careful discipline around plan and apply commands in a team environment. That said, the provider ecosystem is unmatched, and if you are working with more than one cloud provider or many third-party services, Terraform covers the surface area that Pulumi and CDK do not.

Pulumi and CDK both use real programming languages, which means the full capability of those languages is available for expressing infrastructure: typed interfaces, loops, functions, and libraries. In my experience, teams that already know TypeScript adopt CDK or Pulumi faster than they learn HCL, and they end up with infrastructure code that follows the same conventions as their application code. CDK specifically has a strong advantage on AWS: the L2 constructs package sensible defaults for complex resources and significantly reduce the boilerplate compared to writing CloudFormation directly. If your team is AWS-only and TypeScript-first, CDK is the most productive path. If you need multi-cloud or want to avoid CloudFormation's limitations, Pulumi gives you the same language benefits without the AWS lock-in.

Common mistakes

  1. Storing Terraform state locally. Local state works for solo experimentation but is unusable for a team. Set up remote state in S3 with DynamoDB locking before the first shared infrastructure apply. Retrofitting remote state after the fact is a painful migration.
  1. Not using workspaces or stacks for environment separation. Production and staging infrastructure should live in separate state contexts. A single Terraform workspace or Pulumi stack covering multiple environments is a blast radius problem waiting to happen.
  1. Importing existing resources instead of rebuilding them with IaC. The import workflow is useful for adopting IaC incrementally, but importing complex manually-created resources often results in state that is difficult to reason about. Rebuild new resources with IaC where possible and import only what you cannot recreate.
  1. Over-abstracting with modules too early. Custom Terraform modules and CDK constructs add complexity. Build flat, explicit configurations for the first version. Abstract when the repetition is clear and stable, not in anticipation of hypothetical reuse.
  1. Not pinning provider and tool versions. An infrastructure apply that uses a different provider version than the one that originally created the resource can produce unexpected diffs or errors. Pin provider versions in the configuration and update them deliberately.

Where to start

  1. Pick the tool based on your team's language affinity and cloud targets. AWS-only and TypeScript team: CDK. Multi-cloud or GCP/Azure heavy: Terraform. Team that knows Python well and wants flexibility: Pulumi. Do not pick the tool you read about last week; pick the one your team will maintain.
  1. Set up remote state before writing any real infrastructure. For Terraform: S3 bucket plus DynamoDB table, configured in the backend block before the first apply. For Pulumi: the Pulumi Cloud backend or an S3-backed self-hosted option. This step is required for team use.
  1. Start with the networking layer. VPC, subnets, security groups, and IAM roles are the foundation everything else depends on. Getting these right in IaC first establishes the pattern for the rest of the infrastructure.

Related reading

  • Docker Compose to Kubernetes: When to Make the Jump
  • CI/CD Pipeline Design That Scales Past Ten Engineers
  • Cloud Cost Optimization: A Practical Checklist
  • Monorepo vs Multi-Repo: A Decision Framework
FAQ

Frequently asked

Author

The person behind Yashveer Labs

Yashveer Singh, founder of Yashveer Labs. I build full stack systems for clients who care that the thing actually works two years later, not just on launch day. The arc I am on points at machine learning, AI engineering, and cybersecurity. Everything I write here comes from the codebase, not from a content brief. That is the difference and it shows.

Related reading