← Back to Patterns

Why we usually choose Pulumi over Terraform

Pulumi is our default when infrastructure starts behaving like software. Existing Terraform estates can still be the better decision when the migration cost is higher than the operational gain.

By Ivan Richter LinkedIn

Last updated: Sep 1, 2026

5 min read

On this page

For new infrastructure, we usually choose Pulumi when the team already works comfortably in one of its supported languages and expects the platform to contain real reusable logic.

We keep Terraform when an existing estate is coherent, well operated, and not materially constrained by HCL or its module model.

It’s a maintenance decision, not a contest between programming languages and configuration. Both tools manage declarative resource state. The difference is the medium used to build and organize the desired graph.

Pulumi fits infrastructure that needs software structure

Infrastructure often develops concepts larger than one provider resource:

  • a standard service with identity, networking, scaling, secrets, alerts, and domain wiring
  • a governed dataset with IAM, retention, quality policy, and ownership metadata
  • an environment assembled from shared platform components
  • policy derived from organizational metadata
  • repeated resources generated from typed domain input

Pulumi lets us model those concepts with functions, types, packages, tests, and normal refactoring tools. We can validate inputs before registering resources and give stable platform responsibilities explicit APIs.

That expressiveness helps when the abstraction is real. It also makes bad abstraction easier to build. A TypeScript class with generics and dependency injection can still be a terrible way to create a bucket. A real language removes tool constraints, not the need for taste.

Reviewability matters more than expressiveness

The reason to choose Pulumi isn’t that any logic can be written. It’s that the required logic can remain direct and reviewable.

A conditional can appear where the decision is made. A shared component can have a typed contract. Common naming and IAM policy can be ordinary code. Tests and static analysis can use the team’s established tooling.

Keep the resulting resource graph visible through previews. Avoid dynamic behavior dependent on external mutable systems during deployment. Avoid abstraction layers that hide provider resources without enforcing a useful platform contract.

If reviewers must execute a miniature application mentally to know what will be created, Pulumi’s power has been spent badly.

Terraform remains excellent for stable estates

Terraform has a broad ecosystem, a familiar workflow, and a constrained declarative language that many teams operate effectively. Mature modules, policy checks, CI plans, state boundaries, and organizational fluency are assets worth keeping.

Don’t migrate a healthy estate because greenfield Pulumi code would look nicer. The migration must preserve resource identity, state ownership, delivery controls, provider behavior, and team confidence. That work competes with application and platform improvements that may have clearer returns.

Stay with Terraform when:

  • the infrastructure shape is stable
  • modules express the required boundaries cleanly
  • plans remain understandable
  • state and apply paths are disciplined
  • the team can refactor without recurring fear
  • HCL limitations aren’t creating meaningful delivery cost

Continuity isn’t technical debt when it keeps producing safe change.

State doesn’t disappear

Pulumi and Terraform both depend on state, ownership, locking, and controlled mutation. Switching tools doesn’t repair an organization that allows several writers, keeps huge state boundaries, or treats imports and moves casually.

The principles in How we treat Terraform state in team environments apply to Pulumi backends as well: durable remote state, constrained access, one authoritative update path, recoverability, and boundaries that match lifecycle.

Choose Pulumi for a better programming model where that matters, not to escape state discipline.

Provider coverage is a workload check

Evaluate the exact resources and features the platform needs. Both ecosystems can lag a cloud API, differ in schema behavior, or require a lower-level escape hatch.

Build a small proof for critical resources, imports, replacements, secrets, and preview behavior. Check upgrade cadence and ownership of third-party providers. “Supports GCP” is too broad to settle a production estate.

Use native or generic API providers only when their weaker typing and lifecycle semantics are understood. Don’t let the escape hatch quietly become the default implementation for half the platform.

Language alignment has real value

Using TypeScript, Go, Python, C#, Java, or YAML can let infrastructure share linting, testing, packaging, and review skills with the wider engineering team. That can reduce the number of special conventions a small team carries.

Choose the language the infrastructure maintainers can operate, not the one the application happens to use. A company writing TypeScript doesn’t automatically benefit from TypeScript infrastructure if nobody responsible for production understands asynchronous values, package upgrades, or the component model.

Keep the infrastructure dependency graph conservative. A deployment program doesn’t need the application monorepo’s entire framework ecosystem.

Migration needs a concrete payoff

Consider moving an existing Terraform area when one or more costs are persistent:

  • module interfaces are dominated by workarounds and indirection
  • policy or generation logic is hard to express safely
  • environment variance produces repeated HCL machinery
  • refactors and imports consume disproportionate effort
  • the team is already maintaining code generators around Terraform
  • a new platform boundary can migrate independently

Start with a bounded state whose resources can be imported or transferred with a reversible plan. Preserve functional behavior first. Don’t rewrite the platform while changing its state engine.

Measure whether review time, duplication, defects, and change lead time improve. A migration that produces more elegant source and the same operating pain has mostly changed syntax.

Our default

Use Pulumi for greenfield or actively evolving infrastructure when typed reusable components and normal software tooling make the desired resource graph easier to express and review.

Keep Terraform where it’s already calm. Replace it only when the present delivery cost is large enough to fund state migration, retraining, new pipelines, and a period of reduced confidence.

Pulumi is our default because infrastructure frequently becomes software-shaped. Keep existing Terraform when it already works well.

More in this domain: Infrastructure

Browse all

Related patterns