Skip to main content
← All Articles · · 12 min read

Terraform vs Pulumi vs CDK vs Ansible: IaC Tools Compared 2025

Terraform vs Pulumi vs CDK vs Ansible — a no-fluff comparison of the four most popular IaC tools. Which one fits your stack, team, and cloud strategy?

terraformpulumiaws-cdkansibleinfrastructure-as-codedevopscloud

Choosing your infrastructure-as-code (IaC) tool is one of the highest-leverage decisions in a DevOps workflow. Get it right, and provisioning becomes repeatable, reviewable, and fast. Get it wrong, and you’re fighting your tooling while your cloud bill climbs.

Terraform, Pulumi, AWS CDK, and Ansible dominate this space — and they all overlap in frustrating ways. This guide cuts through the noise: what each tool actually does, where each excels, and how to pick the right one for your situation in 2025.


Quick Comparison

TerraformPulumiAWS CDKAnsible
LanguageHCL (custom DSL)TypeScript, Python, Go, Java, C#TypeScript, Python, Java, Go, C#YAML (Jinja2)
Cloud supportMulti-cloud (1,000+ providers)Multi-cloud (1,000+ providers)AWS onlyMulti-cloud + on-prem
State managementRequired (local or remote)Required (local or remote)CloudFormation stacksStateless
Learning curveModerate (HCL is simple)High (real code + IaC concepts)High (CDK + CloudFormation mental model)Low (YAML-first)
PricingOpen source + HCP TerraformOpen source + Pulumi CloudFree (pay for AWS resources)Open source + AAP
IdempotencyYes (plan/apply)Yes (preview/up)Yes (deploy)Best-effort (depends on modules)
Primary use caseMulti-cloud provisioningDev-team-friendly provisioningAWS-native teamsConfig management + provisioning

Shortest answer: Terraform for multi-cloud teams. Pulumi if your engineers hate HCL. CDK if you’re all-in on AWS. Ansible for configuration management and hybrid/on-prem environments.


Why IaC Tool Choice Matters in 2025

Infrastructure-as-code has become table stakes. The question is no longer should we use IaC but which IaC — and the wrong choice creates real costs:

  • Lock-in risk: CDK compiles to CloudFormation, which means you’re coupled to AWS’s deployment model.
  • Skill portability: HCL and YAML skills transfer easily; Pulumi TypeScript knowledge is less portable across IaC tools.
  • Debugging overhead: State drift in Terraform, CloudFormation stack failures in CDK, idempotency issues in Ansible — each has a distinct failure mode.
  • Team velocity: A tool your developers don’t understand well becomes a bottleneck. Ansible’s low barrier matters for teams without dedicated DevOps.

Terraform Deep Dive

Terraform, by HashiCorp, has been the default choice for multi-cloud provisioning since 2014. It uses HCL (HashiCorp Configuration Language) — a purpose-built DSL that’s more human-readable than JSON/YAML and simpler than general-purpose code.

How it works

You declare the desired state in .tf files. Terraform calculates a plan (what will change) and applies it by calling cloud provider APIs. State is stored in a .tfstate file — locally or in a backend like S3, HCP Terraform, or Terraform Cloud.

provider "aws" {
  region = "us-east-1"
}

resource "aws_s3_bucket" "app_assets" {
  bucket = "my-app-assets-prod"

  tags = {
    Environment = "production"
    Team        = "platform"
  }
}

resource "aws_s3_bucket_versioning" "app_assets" {
  bucket = aws_s3_bucket.app_assets.id
  versioning_configuration {
    status = "Enabled"
  }
}

The provider ecosystem

Terraform’s biggest strength is breadth. The Terraform Registry has over 3,000 providers covering AWS, GCP, Azure, Cloudflare, Datadog, GitHub, PagerDuty, and virtually any service with an API. Pulumi actually uses these same providers under the hood.

State management

State is how Terraform knows what exists. This is powerful (enables drift detection, targeted applies) and painful (state corruption is a real failure mode, remote state locking is critical in teams).

Use remote state from day one. S3 + DynamoDB for locking is the standard AWS pattern. HCP Terraform handles this automatically.

Best for

  • Multi-cloud or non-AWS infrastructure
  • Teams that need the widest provider coverage
  • Organizations already using the HashiCorp stack (Vault, Consul, Nomad)
  • Any team that wants the largest community and most Stack Overflow answers

Watch out for

  • HCL has no real loops, conditionals, or abstraction beyond modules — complex logic gets ugly
  • State file management overhead in large teams
  • HashiCorp changed the license to BSL in 2023 (OpenTofu is the open-source fork if that matters to you)

Pulumi Deep Dive

Pulumi lets you define infrastructure using real programming languages: TypeScript, Python, Go, Java, and C#. You get loops, functions, classes, tests, and IDE support that HCL simply can’t match.

How it works

Pulumi compiles your program and communicates with a state backend (Pulumi Cloud, S3, Azure Blob, etc.) to manage resources. The cloud providers are largely the same as Terraform (many are bridged from Terraform providers).

import * as aws from "@pulumi/aws";

const bucket = new aws.s3.BucketV2("appAssets", {
  tags: {
    Environment: "production",
    Team: "platform",
  },
});

const versioning = new aws.s3.BucketVersioningV2("appAssetsVersioning", {
  bucket: bucket.id,
  versioningConfiguration: {
    status: "Enabled",
  },
});

export const bucketName = bucket.bucket;

Where Pulumi shines

Real code means real abstraction. You can write a function that generates a standard “secure S3 bucket” configuration and reuse it across 50 environments. You can write unit tests for your infrastructure logic. You can use npm/pip packages. You can auto-generate documentation from types.

// A reusable component — impossible to express cleanly in HCL
function createSecureBucket(name: string, environment: string) {
  const bucket = new aws.s3.BucketV2(name, {
    tags: { Environment: environment },
  });

  new aws.s3.BucketPublicAccessBlock(`${name}-public-access`, {
    bucket: bucket.id,
    blockPublicAcls: true,
    blockPublicPolicy: true,
    ignorePublicAcls: true,
    restrictPublicBuckets: true,
  });

  return bucket;
}

Best for

  • Developer-led infrastructure teams that prefer real languages
  • Complex infrastructure with significant shared logic/components
  • Monorepos where infra and app code live together
  • Teams that want to write tests for their IaC

Watch out for

  • Higher learning curve: you need to understand both the language and Pulumi’s resource model
  • Debugging can be harder (multiple abstraction layers)
  • Smaller community than Terraform
  • Pulumi Cloud has a generous free tier but production teams often need paid plans for team features

AWS CDK Deep Dive

The AWS Cloud Development Kit (CDK) lets you define AWS infrastructure using TypeScript, Python, Java, Go, or C#. Unlike Pulumi, CDK compiles to CloudFormation templates — AWS’s native deployment engine.

How it works

You write CDK code, run cdk synth to generate CloudFormation templates, then cdk deploy to deploy them. CloudFormation manages the actual resource lifecycle.

import * as cdk from "aws-cdk-lib";
import * as s3 from "aws-cdk-lib/aws-s3";

export class AppStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const bucket = new s3.Bucket(this, "AppAssets", {
      versioned: true,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      removalPolicy: cdk.RemovalPolicy.RETAIN,
    });
  }
}

The CloudFormation layer

This is CDK’s biggest feature and biggest limitation. CloudFormation means you get AWS’s native state management, rollback on failure, drift detection, and change sets — no separate state file to manage. But you’re also subject to CloudFormation’s quirks: stack limits (500 resources), slow deployments (especially for Lambda), and opaque error messages.

CDK Constructs are the killer feature. AWS publishes high-level constructs like ApplicationLoadBalancedFargateService that provision an entire ECS + ALB + Route53 setup in a few lines.

// 3 lines, ~15 AWS resources created
new ecs_patterns.ApplicationLoadBalancedFargateService(this, "Api", {
  cluster,
  taskImageOptions: { image: ecs.ContainerImage.fromRegistry("nginx") },
  publicLoadBalancer: true,
});

Best for

  • AWS-only teams that want native CloudFormation integration
  • Teams already using CDK Pipelines (CI/CD)
  • Orgs that want AWS-managed rollback without managing state
  • Use cases that benefit from high-level CDK constructs

Watch out for

  • AWS-only — no GCP, Azure, or third-party provider support
  • CloudFormation deployments are slow compared to direct API calls
  • You need to understand both CDK and CloudFormation to debug issues
  • Stack resource limits can bite you at scale

Ansible Deep Dive

Ansible is different from the other three in a fundamental way: it’s a configuration management tool first, provisioning tool second. Where Terraform/Pulumi/CDK describe desired resource state, Ansible executes playbooks — ordered sequences of tasks.

How it works

Ansible connects to target machines (or cloud APIs) over SSH (or WinRM) and executes tasks. It’s agentless — no daemon running on targets. Playbooks are YAML files with Jinja2 templating.

---
- name: Provision S3 bucket
  hosts: localhost
  connection: local

  tasks:
    - name: Create S3 bucket
      amazon.aws.s3_bucket:
        name: my-app-assets-prod
        state: present
        versioning: true
        tags:
          Environment: production
          Team: platform

Configuration management vs provisioning

Ansible’s strength is configuring what’s already running. Deploying a Nginx config, updating a package, rotating a certificate, running a database migration — these are Ansible’s natural habitat.

As a provisioning tool, Ansible is weaker. It lacks a state model, so it can’t tell you what drift exists. Idempotency depends on each module’s implementation; some modules are idempotent by default, others are not. Rollbacks are manual.

Inventory and targeting

Ansible inventories define what hosts to manage. Static inventories are simple YAML/INI files. Dynamic inventories query AWS/GCP/Azure APIs to build the host list at runtime.

# inventory.yml
all:
  children:
    webservers:
      hosts:
        web1.example.com:
        web2.example.com:
    databases:
      hosts:
        db1.example.com:

Best for

  • Configuration management: OS packages, services, config files, users
  • Hybrid/on-prem environments where SSH access is standard
  • Day-2 operations: patching, compliance, certificate rotation
  • Teams that need a low-barrier automation tool (YAML is approachable)
  • Orchestrating multi-step deployments across existing servers

Watch out for

  • Not the right tool for provisioning immutable cloud infrastructure
  • Large playbooks become hard to maintain without roles and structure
  • No native state: drift goes undetected until something breaks
  • Performance at scale is slower than purpose-built provisioning tools (SSH round-trips)

Head-to-Head Comparison

Learning curve

Easiest to hardest: Ansible → Terraform → CDK → Pulumi

Ansible YAML is immediately readable to anyone who’s written a CI config. Terraform HCL takes a day to get productive with. CDK requires learning both the CDK API and CloudFormation concepts. Pulumi has the steepest curve — you’re writing real code but need to internalize Pulumi’s resource/output model.

Multi-cloud support

Best to worst: Terraform ≈ Pulumi → Ansible → CDK

Terraform and Pulumi both have excellent multi-cloud coverage. Ansible can provision across clouds but lacks the state management to do it reliably at scale. CDK is AWS-only.

Team size considerations

Team sizeBest choice
Solo / small startupTerraform (or CDK if AWS-only)
Mid-size (5-20 engineers)Terraform or CDK
Large / platform teamPulumi (code-based reuse) or Terraform with modules
Ops-heavy / sysadmin teamAnsible for config, Terraform for provisioning

Migration cost

Migrating between IaC tools is painful. Terraform ↔ Pulumi has community tools but still requires significant effort. CDK → Terraform means exporting CloudFormation and re-implementing. Ansible → any declarative tool is a rewrite.

Start with the right tool. If you’re greenfield, the migration cost is a reason to choose carefully now.


Decision Matrix: When to Choose Each

SituationBest choiceWhy
Multi-cloud (AWS + GCP + Azure)TerraformWidest provider coverage
All-in on AWS, team loves TypeScriptCDKNative CloudFormation, excellent constructs
Dev team that hates HCLPulumiReal language, real IDE support
Config management / on-prem serversAnsibleAgentless, SSH-native, day-2 ops
Complex reusable infrastructure logicPulumiLoops, functions, tests in real code
Small team, fast startTerraformLargest community, most examples
Gradual migration from manual opsAnsibleLow barrier, procedural model is familiar
Need CloudFormation’s rollback guaranteesCDKCompiles to CF, gets native stack management

FAQ

Can I use Terraform and Ansible together?

Yes — this is actually a common pattern. Terraform provisions immutable infrastructure (EC2 instances, RDS, VPCs). Ansible configures what’s running on those instances (packages, services, app config). They complement each other well.

Is Pulumi actually better than Terraform for large teams?

For teams with strong TypeScript or Python skills and complex shared infrastructure logic, yes. The ability to write tests, create typed abstractions, and enforce standards via code is genuinely powerful. For most teams, Terraform’s simpler mental model and larger community win.

Should I use CDK if I’m already using CloudFormation?

CDK is a significant improvement over raw CloudFormation templates. The constructs are excellent and the developer experience is much better. If you’re maintaining hand-written CloudFormation, migrating to CDK is worth it.

What about OpenTofu — should I use it instead of Terraform?

OpenTofu is the open-source fork of Terraform created after HashiCorp’s BSL license change in 2023. It’s compatible with Terraform HCL and modules. If license concerns matter to your organization, OpenTofu is a solid drop-in replacement.

Does Ansible replace Terraform?

No. Ansible can provision cloud resources, but it lacks the state management that makes declarative provisioning reliable. For anything beyond simple scripts, use Terraform/Pulumi/CDK for provisioning and Ansible for configuration management.


Conclusion

There’s no universally best IaC tool — there’s the best tool for your specific constraints.

  • Terraform if you need multi-cloud, the biggest community, and the most provider coverage.
  • Pulumi if your team wants to write real code and HCL feels limiting.
  • AWS CDK if you’re AWS-native and want CloudFormation’s managed deployment model.
  • Ansible if you’re managing existing servers, need configuration management, or want a low-barrier automation tool for hybrid environments.

For most teams starting fresh on AWS: Terraform is the safe default. For AWS-only teams with strong TypeScript experience: CDK is compelling. For teams that find themselves fighting HCL’s limitations: Pulumi is the answer.


Related tools on DevPlaybook:

Free Download

50 CLI Commands That Save Hours Every Week

The exact Git shortcuts, file search tricks, Docker commands, and productivity hacks that senior developers use daily. Compiled from 5+ years of shipping production code.

  • Git mastery (10 cmds)
  • File & search wizardry
  • Docker & DevOps tricks
  • Process & network tools
  • Productivity boosters
  • macOS & Linux install guide

No spam. 1 email/week max. Unsubscribe anytime.

Found this guide useful? Check out our free developer tools.