Terraform has been the default for infrastructure as code for nearly a decade. Pulumi has been the credible alternative for about half that time. The debate often gets framed as "HCL vs real programming languages," which is technically right and practically misses the point — neither language is the dominant factor for most teams.
This post is the actual decision: what each tool does well, what each does poorly, and how to pick based on team and use case rather than language preference.
The Short Version
- Terraform. HCL declarative language. Massive ecosystem. Industry standard. The safe default.
- Pulumi. Use real languages (TypeScript, Python, Go, C#) to define infrastructure. Better for complex logic, smaller ecosystem.
Both have a real reason to exist. Neither is universally better.
The Language Question
Terraform uses HCL — HashiCorp Configuration Language. Declarative, purpose-built for infrastructure, intentionally limited in expressiveness.
resource "aws_instance" "web" {
count = var.instance_count
ami = var.ami_id
instance_type = var.instance_type
tags = {
Name = "web-${count.index}"
Environment = var.environment
}
}
Pulumi uses real programming languages.
import * as aws from "@pulumi/aws";
for (let i = 0; i < instanceCount; i++) {
new aws.ec2.Instance(`web-${i}`, {
ami: amiId,
instanceType,
tags: {
Name: `web-${i}`,
Environment: environment,
},
});
}
The Pulumi version uses a for loop, conditionals, and any other language feature you want. The Terraform version uses count and for_each and dynamic blocks — adequate for most needs, awkward for complex logic.
When complex logic matters: Pulumi. When complex logic is a smell: Terraform.
Ecosystem Size
This is the biggest practical difference.
Terraform has been around longer and has a massive ecosystem:
- Every cloud provider's full API surface, well-maintained
- Hundreds of community modules for common patterns
- Strong integration with policy tools, drift detection, cost tools
- Extensive Stack Overflow and tutorial content
- Most senior infrastructure engineers know it
Pulumi's ecosystem is competent but smaller:
- All major cloud providers covered
- Fewer community modules; more "you write it yourself"
- Smaller community for off-the-shelf solutions
- Less reference content
- Hiring requires "Pulumi experience" or strong cloud expertise
If you need a specific niche capability, Terraform probably has it. With Pulumi, you may write it yourself.
State Management
Both tools track state — the mapping between declared resources and real cloud resources.
Terraform state: A JSON file, typically stored in S3, Azure Blob, or Terraform Cloud. State locking via DynamoDB or equivalent. Manual state operations (terraform state mv, terraform import) are sometimes needed.
Pulumi state: Stored in Pulumi Cloud (free for individuals, paid for teams) or self-hosted backends (S3, Azure Blob). Similar locking model. State is more abstracted; less direct manipulation.
The state model is roughly equivalent. Pulumi's commercial offering is more polished for state management; Terraform's open-source state is more flexible.
Modules and Reusability
Both support reusable modules. The expression differs.
Terraform modules:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.0"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
}
The terraform-aws-modules collection is community gold — thousands of stars, battle-tested modules for common AWS patterns.
Pulumi components:
class StandardVpc extends pulumi.ComponentResource {
constructor(name: string, args: VpcArgs) {
super("yourorg:network:Vpc", name);
// Create VPC, subnets, route tables, etc.
}
}
const myVpc = new StandardVpc("main", { cidr: "10.0.0.0/16" });
Pulumi components are real classes with real inheritance and composition. Terraform modules are wrapped configuration. For complex abstractions, Pulumi components are more powerful. For "I just need a VPC," Terraform modules are easier.
Testing
Pulumi wins on testability. Real languages have real testing frameworks:
import * as pulumi from "@pulumi/pulumi";
import { expect } from "chai";
describe("StandardVpc", () => {
it("creates expected subnets", async () => {
const vpc = new StandardVpc("test", { cidr: "10.0.0.0/16" });
expect((await vpc.subnetCount).toEqual(6));
});
});
Terraform testing has improved with terraform test in recent versions, but it remains less natural than testing real code with real assertions.
For complex modules with logic, Pulumi's testing story is significantly better. For simple modules, both work.
Migration Stories
Terraform to Pulumi: Pulumi has pulumi convert that ingests Terraform code and produces Pulumi code. Works for the simple cases; complex Terraform requires manual rework.
Pulumi to Terraform: No automated path. Manual rewrite.
Picking either is mostly a one-way commitment. Realistically, neither migration happens often once a team is established.
Performance and Plan Speed
Both tools work by reading current state, comparing to desired state, and producing a plan. Performance is roughly equivalent for similar-sized infrastructures.
Terraform's plan can slow down dramatically with state files containing thousands of resources. Pulumi has similar issues at scale. Both have workarounds (state splitting, micro-stacks).
Neither tool's performance is the bottleneck for most teams. The bottleneck is usually the cloud APIs themselves.
Day-to-Day Workflow
Terraform workflow:
terraform init
terraform plan
terraform apply
Pulumi workflow:
pulumi up # plan + apply in one
pulumi preview # plan only
Pulumi's workflow is slightly tighter. Terraform's is more granular. Both have CI/CD integration that hides the local CLI.
For day-to-day use, neither is markedly better. The tools' differences emerge in larger workflows — multi-environment, multi-team, multi-stack.
Cost
Terraform:
- Open source: free
- Terraform Cloud (state, runs, policy): free up to 5 users, then paid
- Self-hosted at scale: free, but you operate it
Pulumi:
- Open source CLI: free
- Pulumi Cloud (default state backend): free for individuals, paid for teams
- Self-hosted backend: free, but state on your own infrastructure
Pulumi's "pay for the SaaS" model is more direct. Terraform's commercial story is murkier (Terraform Cloud, Enterprise, plus the various third-party automation tools).
For most teams, the cost difference is marginal. The decision is on the technical and ecosystem axes, not pricing.
Decision Heuristics
Pick Terraform when:
- Your team is hiring from a broad pool (most infra engineers know it)
- You rely on community modules for common patterns (VPCs, EKS, RDS)
- You prefer declarative over imperative for infrastructure
- The infrastructure logic is straightforward
Pick Pulumi when:
- You have complex orchestration logic (loops, conditions, abstractions)
- Your team is comfortable with TypeScript or Python and wants real testing
- You are building infrastructure abstractions for many consumers (a platform team)
- You want type safety in your IaC
Pick something else when:
- The infrastructure is small enough that any tool is overkill — just use the cloud's CLI
- The infrastructure is huge and dynamic — you may need a different abstraction (Crossplane, Kubernetes-native)
- The team is locked into a vendor-specific tool (CloudFormation, ARM templates)
The Hybrid
Some teams use both:
- Terraform for cloud-level resources (VPCs, IAM, account-level setup)
- Pulumi for application-level resources (Kubernetes deployments, environment-specific config)
This is uncommon but works. The cost is two tools to maintain; the benefit is using the right tool for each layer.
Most teams should pick one and stick with it. The mental tax of context-switching between two IaC tools is real.
What Holds Up
Five years from now:
- Terraform will still be the bigger ecosystem
- Pulumi will still be the better fit for complex abstractions
- New tools (Winglang, System Initiative, others) will continue to emerge
- The "right" choice will still depend on team and use case
The honest answer for most teams in 2026: Terraform unless you have a specific reason to pick differently. The ecosystem advantage compounds, and "specific reason" is usually present in mature platform teams or in code-heavy infrastructure contexts.
Picking an IaC tool for a new infrastructure project, or evaluating whether to migrate? We help teams pick stacks based on team capability and what the infrastructure actually looks like, not vendor pitches. scopeforged.com