We tried Pulumi for a quarter and went back to Terraform. Both are real options. Why we picked one and what would change our mind.
We had Pulumi running alongside Terraform for about a quarter while we evaluated whether to switch. Pulumi is good. We went back to Terraform anyway. This post is the comparison from someone who's run both, with honest reasoning about where each fits.
Terraform: declarative DSL (HCL). You describe desired state in .tf files. Provider plugins translate to API calls.
Pulumi: programming language (TypeScript, Python, Go, C#). You write code that constructs resources. Same provider model under the hood.
Both target the same cloud APIs. Both maintain state. Both have plan-then-apply workflows. The fundamental difference is the language.
The advantages of "infrastructure in a real programming language":
Loops and conditionals are normal. In Terraform, for_each and dynamic blocks work but feel like hacks. In Pulumi, for (const env of envs) { ... } is just code.
Reusable abstractions. Functions, classes, modules. The same ergonomics as application code.
Type safety (in TypeScript / Go / C#). Compiler catches mistakes Terraform's plan would flag at runtime.
Familiarity. Engineers who already know TypeScript/Python can write infrastructure without learning HCL.
Native testing. Unit tests on infrastructure code using normal testing frameworks.
For complex infrastructure with lots of conditional logic, Pulumi's expressiveness is real. We could see why teams pick it.
The disadvantages we hit:
Less predictable plans. Pulumi's plan output for complex programs can be hard to read. Terraform's plan output, while sometimes verbose, is structured: each resource shows clearly what's changing. Pulumi's output reflects the program structure.
Refactoring affects state. Renaming a variable or restructuring code can confuse Pulumi about which resource is which, leading to plan diffs that propose recreating things. Terraform's resource addresses (e.g., aws_instance.web[0]) are more stable.
Smaller community. Stack Overflow, blog posts, examples — Terraform has 5-10x the volume. When you hit an obscure problem, "how do other people solve this" is harder.
State portability. Pulumi state is in their service or in S3 in their format. Terraform state is in S3 in a documented format. Migrating between Pulumi and Terraform is a project; same direction either way, but Terraform's state format is more interoperable with tools.
The "code" can become messy. With a real programming language, the temptation to add complexity is real. Helper functions, classes, abstractions. Unconstrained, infrastructure code becomes hard to read. HCL's limitations force simplicity.
More moving parts. A Pulumi program runs as a process; needs Node/Python/Go runtime. Terraform is a single binary. Operational simplicity matters at scale.
We migrated 3 services to Pulumi over the quarter:
The third project is what tipped us back to Terraform. The code was harder to review than equivalent Terraform. Less obviously what infrastructure was being created.
The thing that mattered most: plan reviewability.
When a Terraform PR comes in, the plan output shows: "Adding aws_instance.X. Modifying aws_instance.Y. Destroying aws_instance.Z." Reviewing the plan is a clear exercise.
With Pulumi, the same review requires reading the code AND the plan. The code might do if (env === 'prod') createDatabaseCluster(); the plan shows the resources but not the conditions. To know "what happens when this PR merges in prod vs staging," I have to read the code AND the per-environment plans.
For our team's workflow (review-heavy infrastructure changes), Terraform's transparent plan was the deciding factor.
A team for whom Pulumi might be the right answer:
If your shape is one of these, Pulumi is genuinely a reasonable pick.
For teams like ours:
Most teams I've talked to are in this category, which is why Terraform remains the more common choice.
A few other options:
AWS CDK: similar concept to Pulumi but AWS-only. Generates CloudFormation under the hood. Some teams love it; we haven't tried it because we want something multi-cloud.
Crossplane: Kubernetes-native infrastructure management. CRDs for cloud resources. Interesting model — your "infrastructure as code" lives as Kubernetes objects. Steep learning curve; we don't use it.
Pure CloudFormation / ARM / Deployment Manager: cloud-vendor-specific. Avoid for multi-cloud reasons.
Configuration management tools (Ansible/Chef/Puppet) for cloud resources: doesn't fit the "desired state" model as cleanly. We use Ansible for inside-host config, not for cloud resources.
Honest acknowledgments:
Both manage state correctly. State files, locking, refresh — well-developed in both.
Both have good provider coverage. AWS, GCP, Azure providers in both are mature.
Both support modules / reusable abstractions. Different syntax, similar capability.
Both work in CI/CD pipelines. Plan in PR, apply on merge. Standard pattern in both.
Both have rough edges. Edge cases where the tool surprises you. Both communities have lived through them.
If you're already on one and considering the other, the migration is real work:
Terraform to Pulumi: Pulumi has an import feature that helps. Per resource, define it in Pulumi and import the existing state. Tedious for many resources.
Pulumi to Terraform: Similar process in reverse. Possible; not fun.
Both directions: state is rewritten; resources are re-imported. Plan to do it carefully, in non-prod first.
The migration cost is high enough that the existing tool's flaws have to be significant to justify.
For both, the tool itself is essentially free (open source). The "cost" is:
Pulumi's hosted state service is convenient but has per-user pricing. Self-hosted (S3 backend) is free for both.
If you're a small team without a dedicated platform engineer, Pulumi's familiarity might win.
If you have a platform team and a large existing Terraform codebase, Terraform's stability and plan transparency are hard to beat.
Try both for a real project, not a toy. Toy projects don't reveal the rough edges.
Plan reviewability matters more than people expect. When you're approving infrastructure changes, you want to know exactly what's about to happen.
The community size matters at scale. When you hit an obscure problem, the size of "people who've solved this" determines how fast you recover.
Don't migrate without a clear reason. Both work. The cost of switching is meaningful.
The "Terraform vs Pulumi" debate is real but most teams will be fine with either. Pick based on team fit; don't agonize. Where teams get into trouble is treating the tool as the goal — "we use Terraform" or "we adopted Pulumi" — rather than focusing on what either tool is for: predictable, reviewable, automatable infrastructure changes. With either tool, get that part right; the rest is details.
Get the latest tutorials, guides, and insights on AI, DevOps, Cloud, and Infrastructure delivered directly to your inbox.
Concrete systemd unit patterns that reduced flakiness: restart policies, resource limits, and structured logs.
A real story of removing console-only changes, adding drift detection, and getting Terraform back in charge.
Explore more articles in this category
Backups are easy. Restores are hard. The quarterly drill we run, what's failed during it, and the discipline that makes "we have backups" actually mean something.
Replication is the foundation of database HA. What we monitor, how we practice failover, and the gotchas that show up only when you actually fail over.
Why Postgres connection limits bite at unexpected times, the pooling layer we put in front, and the pool-mode tradeoffs we learned the hard way.