Terraform vs Ansible vs Pulumi: Picking Your IaC Tool

Abstract technical illustration for the article: Terraform vs Ansible vs Pulumi: Picking Your IaC Tool

Terraform, Ansible, or Pulumi? All three automate infrastructure, all three are open source, and all three appear in the same job descriptions. The confusion clears up once you see they are not three flavors of one thing, but three answers to one question: how do you tell a machine what your infrastructure should look like?

This article explains how each tool actually works, where the trade-offs bite, and gives you a decision framework with realistic scenarios — so you can pick on evidence, not hype.

Three tools, three philosophies

Terraform says: describe the infrastructure you want in a purpose-built declarative language, and let the tool figure out the steps. Ansible says: describe a sequence of tasks in YAML, and the tool executes them on your machines in order. Pulumi says: describe the infrastructure you want, but in a real programming language like Python or TypeScript — and let its deployment engine work out the steps.

Declarative versus procedural is the dividing line. With Terraform and Pulumi you declare an end state (“there is an S3 bucket with this policy”) and the tool reconciles reality toward it. With Ansible you describe procedures (“install nginx, copy this config, restart”). Its modules are idempotent — re-running is safe — but Ansible keeps no memory of what it has done. That shapes everything: state management, drift detection, and which problems each tool is genuinely good at.

Terraform: declare the end state, keep the state

Terraform, originally created by HashiCorp, is the default choice for cloud provisioning. You write HCL (HashiCorp Configuration Language) — a declarative format that reads easily even for non-programmers:

resource "aws_s3_bucket" "reports" {  bucket = "acme-reports-2026"}resource "aws_instance" "web" {  ami           = "ami-0c55b159cbfafe1f0"  instance_type = "t3.micro"  tags = { Name = "web-01" }}

The workflow is three commands: terraform init downloads providers (plugins translating Terraform calls into cloud API calls), terraform plan shows a diff of what will change — git diff for infrastructure — and terraform apply makes the changes. The Terraform Registry hosts thousands of providers covering AWS, Azure, GCP, Kubernetes, GitHub, Datadog, Cloudflare, and more: one tool and one workflow, wherever your infrastructure lives.

The other half of Terraform is the state file (terraform.tfstate): the mapping between your configuration and the real resources it created. Every plan compares three things — your configuration, the state file, and the actual cloud. That is what makes drift visible and incremental updates possible. The catch: the state file is the memory. Lose it and Terraform forgets it made anything. In teams it lives in a remote backend (an S3 bucket, Azure Blob storage, Terraform Cloud) with locking, versioning, and encryption — treat it as production data.

Honest limitations: complex logic (nested loops, conditionals) gets awkward in a DSL, and the module system is less expressive than npm or PyPI. Since the 2023 Business Source License switch, the community maintains OpenTofu — a Linux Foundation fork sharing the same syntax.

Ansible: automate the steps, over SSH, no agents

Ansible, owned by Red Hat, is agentless: nothing to install on managed machines. It connects over SSH (or WinRM for Windows), pushes small programs called modules to the target, executes them, and removes them when finished. If you can SSH into a server, Ansible can manage it — no bootstrap project, no daemon fleet to patch.

Playbooks are YAML files that read like a checklist:

- name: Configure web servers  hosts: web  become: true  tasks:    - name: Install nginx      apt:        name: nginx        state: present    - name: Deploy site config      copy:        src: site.conf        dest: /etc/nginx/conf.d/site.conf    - name: Restart nginx      service:        name: nginx        state: restarted

Each module is idempotent: if nginx is already installed, the task reports “ok” and changes nothing. Running a playbook twice is safe — which makes Ansible ideal for the day-two work Terraform avoids: OS configuration, app deployment, certificate rotation, fleet patching, rolling restarts.

The trade-off is structural. Ansible is stateless: it cannot preview changes, detect manual drift, or tear down a fleet cleanly — there is no terraform destroy equivalent. You can provision cloud resources with Ansible, but it is blunt there: sequential API calls, manual ordering, no drift detection. Its sweet spot is everything that happens after the machine exists.

Pulumi: a real programming language with a declarative engine

Pulumi's pitch: you should not have to learn a new DSL. You write a program in a general-purpose language — TypeScript, JavaScript, Python, Go, .NET languages, Java, or YAML — using the loops, classes, package managers, and IDE tooling your team already knows:

import * as aws from "@pulumi/aws";const bucket = new aws.s3.BucketV2("reports", {  bucket: "acme-reports-2026",});const server = new aws.ec2.Instance("web", {  ami: "ami-0c55b159cbfafe1f0",  instanceType: "t3.micro",  tags: { Name: "web-01" },});export const bucketName = bucket.id;

The key point: despite imperative-looking languages, Pulumi is declarative under the hood. Your program declares resource objects; the deployment engine computes the operations to reach that desired state. pulumi up shows a plan for review, just like terraform plan, before anything changes. The real language pays off where DSLs are weakest: loops over repetitive resources, computed values, code sharing through normal packages, and unit tests with standard frameworks.

Pulumi also keeps state — by default in Pulumi Cloud, adding access control, secrets management, drift detection, and change history. Self-managed backends (S3, Azure Blob Storage, GCS, local files) are supported. Secrets are first-class: encrypted in transit and at rest in the state file, with pluggable KMS providers. The project is open source under the Apache 2.0 license.

Honest limitations: the ecosystem is smaller and younger — fewer community modules, fewer answered questions. A real language tempts teams into overly clever code that nobody wants to review. And if your team is not comfortable in a supported language, the advantage evaporates: YAML people will do better in Terraform than in forced TypeScript.

The differentiators that actually matter

State management and drift

Terraform and Pulumi maintain state: both detect drift and both preview changes before applying them. Ansible keeps no state, so it can do neither. That single difference is why Ansible is a poor choice as your only infrastructure tool: without state, you cannot answer “what did we create, and what changed since?”

Provisioning versus configuration

Terraform and Pulumi provision cloud resources — machines, networks, databases, DNS. Ansible configures what runs on the machines. Neither covers the other well: a Terraform provisioner running scripts on a new VM is a known anti-pattern, and an Ansible playbook creating VPCs fights the tool. The industry pattern is Terraform or Pulumi for provisioning, Ansible for configuration — often in the same pipeline.

Language and learning curve

Ansible's YAML is the easiest on-ramp: readable by anyone, running in an afternoon. HCL sits in the middle: a small language with a big conceptual surface (state, providers, modules). Pulumi needs no new syntax if you already write TypeScript or Python — but it demands real programming skill, a higher floor than Ansible. The right question is not “which is simplest” but “which matches the team that will maintain this at 2 AM?”

Ecosystem, hiring, and maturity

Terraform wins outright: thousands of providers, the largest community, a line on nearly every DevOps job description. Pulumi's registry is broad (it can adapt any Terraform provider) but community knowledge is thinner. Ansible's ecosystem is huge but centered on configuration — its Galaxy roles are a config asset, not a provisioning one. Optimizing for hiring? Terraform is the safe default.

The decision framework: which tool for which job

  • Provisioning cloud infrastructure across providers (VPCs, databases, Kubernetes clusters, DNS). Pick Terraform for the largest ecosystem and hiring pool, or Pulumi if your team already writes a supported language and wants loops, abstractions, and real testing. This is their home turf.
  • Configuring servers and deploying applications (packages, config templates, rolling restarts, fleet patching). Pick Ansible — agentless over SSH, idempotent modules, YAML that reads like documentation. Do not provision your VPC with it.
  • Dynamic infrastructure logic — environments generated from data, per-tenant resources computed at deploy time, infra shared as versioned packages. Pick Pulumi: a real language beats a DSL here.
  • Your team is small and mixed-skill, and just needs servers kept consistent. Start with Ansible; add Terraform when cloud provisioning grows. The learning curve is measured in hours, and you can manage a machine the moment you can SSH into it.
  • You need one standard the whole org can hire for. Pick Terraform. Network effects compound: modules, community answers, and Terraform-shaped candidates in the hiring market.
  • Building a platform that provisions environments programmatically — per-branch preview environments, self-service portals. Pick Pulumi for its Automation API: a host application drives deployments as a library, not a CLI.

The honest answer: use two of them

The sharpest teams do not pick one. The canonical pairing is Terraform (or Pulumi) for provisioning, Ansible for configuration: Terraform creates the VMs and load balancers, then Ansible configures the software on them. The only wrong answer is using one tool for both jobs and pretending the mismatch does not exist.

Further reading

Similar Posts

Leave a Reply