Terraform vs Pulumi vs CloudFormation: Choosing an IaC Tool
Terraform vs Pulumi vs CloudFormation: Choosing an IaC Tool
Terraform, Pulumi, and CloudFormation all manage infrastructure as code, but they differ in three decisive ways: the language you write in, how state is managed, and how deeply they integrate with their cloud. This guide compares them on those dimensions and gives you a decision framework.
The Three Approaches
Terraform: declarative HCL (HashiCorp Configuration Language), state stored in backends, multi-cloud via providers.
Pulumi: infrastructure in real programming languages (TypeScript, Python, Go), state in the Pulumi service or self-hosted.
CloudFormation: AWS-native, YAML/JSON templates, state managed by AWS itself.
Language: The First Decision
# Terraform — HCL, declarative
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-server"
}
}
// Pulumi — TypeScript, imperative with declarative resources
import * as aws from "@pulumi/aws";
const web = new aws.ec2.Instance("web", {
ami: "ami-0c55b159cbfafe1f0",
instanceType: "t3.micro",
tags: { Name: "web-server" },
});
export const publicIp = web.publicIp;
# CloudFormation — YAML, declarative
Resources:
WebInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0c55b159cbfafe1f0
InstanceType: t3.micro
Tags:
- Key: Name
Value: web-server
The language choice is not cosmetic. Pulumi's real-language model enables loops, conditionals, and functions without HCL's awkward expressions:
// Pulumi: real loops and conditionals
const instances = environments.map(
(env) =>
new aws.ec2.Instance(`web-${env}`, {
instanceType: env === "prod" ? "m5.large" : "t3.micro",
tags: { Environment: env },
}),
);
Terraform can do this with for_each and count, but the syntax is harder to read and test. CloudFormation has no loops at all — you generate templates or repeat blocks.
State Management
State is the source of truth for what exists in your cloud.
Terraform: state is a JSON file in a backend (S3, Terraform Cloud, local). You must configure locking and remote storage yourself. State corruption or drift is a real operational concern.
# backend.tf — remote state with locking
terraform {
backend "s3" {
bucket = "my-tf-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "tf-locks"
}
}
Pulumi: state in the Pulumi Cloud service (or self-hosted), with built-in locking, history, and policy. Less to configure, but a dependency on the service.
CloudFormation: AWS manages state entirely. No state files, no locking, no drift files. This is CloudFormation's biggest operational advantage: the state problem is solved for you.
Multi-Cloud
| Tool | Multi-Cloud | Provider Model |
|---|---|---|
| Terraform | Excellent | 1000+ providers, community |
| Pulumi | Excellent | Native providers per cloud |
| CloudFormation | AWS only | N/A |
If you run AWS + GCP + Azure, Terraform or Pulumi are the only real options. CloudFormation locks you to AWS.
Drift and Diffing
All three detect drift between declared and actual state, but the experience differs:
- Terraform:
terraform planshows a precise diff. Drift detection requiresterraform refreshor plan runs. - Pulumi:
pulumi previewshows a diff with the same precision, plus the ability to write tests against the plan. - CloudFormation: drift detection is a separate feature you must enable per stack; diffs are less granular.
Testing Infrastructure Code
This is where Pulumi's language model shines:
// Pulumi: unit-test your infrastructure like application code
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
pulumi.runtime.setMocks({
newResource: (args) => ({ id: `${args.name}_id`, state: args.inputs }),
call: (args) => args.inputs,
});
it("tags all instances with the environment", async () => {
const infra = await import("./infra");
const instance = await infra.web.instance;
expect(instance.tags.Environment).toBe("prod");
});
Terraform has terraform test and third-party tools (Terratest), but testing HCL is inherently harder than testing TypeScript.
Comparison Table
| Aspect | Terraform | Pulumi | CloudFormation |
|---|---|---|---|
| Language | HCL | TS/Python/Go/etc | YAML/JSON |
| State | Backend (you manage) | Pulumi service | AWS-managed |
| Multi-cloud | Yes | Yes | No |
| Loops/conditionals | Awkward | Native | None |
| Testing | Limited | Strong | Limited |
| Ecosystem | Largest | Growing | AWS-native |
| Learning curve | Moderate | Moderate | Low (AWS teams) |
Decision Guide
| Your situation | Choose |
|---|---|
| Multi-cloud, largest ecosystem | Terraform |
| Infrastructure as real code, testing | Pulumi |
| AWS-only, want zero state management | CloudFormation |
| Team already knows HCL | Terraform |
| Team is TypeScript/Python-first | Pulumi |
| AWS CDK alternative (real language, AWS) | CDK (not listed) |
Implementation Checklist
- Decide the language: HCL, real code, or YAML
- Check multi-cloud requirements first
- Evaluate state management: who owns the state problem?
- Prototype loops and conditionals you actually need
- Test the diff/plan experience on your team
- Consider testing requirements for infrastructure
- Plan the migration path from any existing tooling
MatterAI builds frontier AI infrastructure for engineering teams — from inference-optimized models to autonomous coding agents and agentic code reviews.
Explore what we're building:
- Orbital IDE — Autonomous AI coding agent with background agents and deep codebase memory
- AI Code Reviews — Agentic pre-commit reviews across GitHub, GitLab, and Bitbucket
- Axon Models — Frontier-grade reasoning models at 70% lower inference cost
Share this Guide:
More Guides
Model Context Protocol (MCP): Building MCP Servers from Scratch
Build production-grade MCP servers with the TypeScript and Python SDKs. Covers the MCP architecture, stdio and HTTP transports, tools, resources, prompts, and the security model every AI application needs.
16 min readRAG vs Fine-Tuning: When to Use Each for Your LLM Application
Decide between retrieval-augmented generation and fine-tuning with a practical decision framework. Compare cost, latency, freshness, and accuracy, and see working implementations of both approaches.
13 min readGrafana Stack vs SigNoz: Choosing an OpenTelemetry-Native Observability Platform
Compare the Grafana LGTM stack (Loki, Tempo, Mimir) with SigNoz for OpenTelemetry observability: architecture, storage engines, operational overhead, cost at scale, and migration paths.
10 min readLLM Fine-Tuning: LoRA vs QLoRA vs Full Fine-Tuning
Compare full fine-tuning, LoRA, and QLoRA for LLMs with memory requirements, training recipes, and code. Learn which method fits your GPU budget, dataset size, and quality requirements.
14 min readPrompt Injection Defense: Securing LLM Apps Against Jailbreaks, RAG Poisoning, and Tool-Use Exploits
Defend production LLM applications against direct jailbreaks, indirect injection via RAG pipelines, and tool-use exploits with layered defenses, input filtering, and least-privilege tool design.
10 min readContinue Reading
Model Context Protocol (MCP): Building MCP Servers from Scratch
Build production-grade MCP servers with the TypeScript and Python SDKs. Covers the MCP architecture, stdio and HTTP transports, tools, resources, prompts, and the security model every AI application needs.
16 min readRAG vs Fine-Tuning: When to Use Each for Your LLM Application
Decide between retrieval-augmented generation and fine-tuning with a practical decision framework. Compare cost, latency, freshness, and accuracy, and see working implementations of both approaches.
13 min readGrafana Stack vs SigNoz: Choosing an OpenTelemetry-Native Observability Platform
Compare the Grafana LGTM stack (Loki, Tempo, Mimir) with SigNoz for OpenTelemetry observability: architecture, storage engines, operational overhead, cost at scale, and migration paths.
10 min readShip Faster. Ship Safer.
Join thousands of engineering teams using MatterAI to autonomously build, review, and deploy code with enterprise-grade precision.
