All articles
DevOps & CloudJuly 6, 2026 7 min read

Pulumi vs Terraform in 2026: A Migration Story We Almost Regretted

We migrated a mid-sized AWS + Vercel estate from Terraform to Pulumi, hit real walls, and rolled part of it back. Here's what actually happened and when Pulumi is worth it.

Pulumi vs Terraform in 2026: A Migration Story We Almost Regretted

Last year we moved a mid-sized AWS and Vercel estate from Terraform to Pulumi. Six months in we rolled part of it back. This is the honest write-up of what worked, what didn't, and the specific decision points we'd use again.

Why We Even Considered the Move

Our Terraform codebase had grown to roughly 40k lines across 14 stacks: VPCs, ECS services, RDS, S3, CloudFront, a chunk of GCP for data pipelines, and Vercel projects managed via the community provider. It worked. It also had the usual Terraform tax:

  • HCL loops and conditionals that read like a puzzle.
  • Module interfaces that leaked implementation details.
  • Duplicated logic between our TypeScript app code (validation, naming conventions) and our infra code.
  • A for_each on a map(object) that had rewritten resource addresses three times in a year.

We had two real engineers who genuinely enjoyed writing HCL. Everyone else treated the infra repo like a dentist visit. When someone proposed Pulumi with TypeScript — the same language 90% of our stack already uses — the pitch landed.

The pitch, minus the marketing

Pulumi's actual technical value proposition is narrow and honest:

  1. Real language semantics: loops, functions, type systems, package managers.
  2. Shared code between application and infrastructure (config schemas, naming, tagging).
  3. Testability with normal unit test frameworks.
  4. Same-day support for new cloud APIs because most providers are bridged from Terraform providers anyway.

Everything else — "faster iteration", "better DX" — is subjective and depends on your team.

The Migration Plan (And Where It Broke)

We didn't rewrite everything. We picked three targets for the first phase:

  • A new product line (greenfield, easy win).
  • Our Vercel + edge config stack (small, isolated).
  • The ECS platform stack (biggest pain point in Terraform).

We left VPC, RDS, IAM roots, and the GCP data stack on Terraform. That decision aged well.

Importing existing state

Pulumi has pulumi import and there's a tf2pulumi converter, but neither is magic. For the ECS stack we tried the converter first:

pulumi convert --from terraform --out ./pulumi-ecs ./terraform-ecs

The output compiled. It was also unusable — a flat wall of resources with no abstractions, mangled variable names, and none of our module structure. We threw it away and rewrote the stack by hand, then imported the live resources one by one:

pulumi import aws:ecs/service:Service api \
  arn:aws:ecs:eu-west-1:123456789012:service/prod-cluster/api

For ~120 resources this took about three engineer-days including verification. Painful but bounded.

Where the wheels came off

Around week five we hit the first real problem: provider version drift between stacks. Our Terraform stacks pinned aws provider 5.x. Our new Pulumi stacks were on a newer bridged version that had different defaults for aws_s3_bucket server-side encryption. A pulumi up on a shared-ish resource wanted to "correct" the encryption config Terraform had set. Not destructive, but noisy and scary during review.

Lesson: if you're going to run both tools in parallel, treat the resource boundary as a hard contract. No shared resources. No cross-tool drift correction. We ended up adding an explicit ignoreChanges list on the Pulumi side for anything Terraform still owned.

The second problem was subtler. Pulumi's default behavior for secrets is to encrypt them in state using a passphrase or a KMS key. That's great. It also means every developer needs decrypt access to run pulumi preview. On Terraform we had gotten away with a small circle of people who could touch state; on Pulumi we suddenly had to design a proper access model for stack config secrets across ten engineers. Not a bug, just work we hadn't scoped.

What Actually Got Better

Being honest: some things improved a lot.

Shared code with the application

Our biggest win was the tagging and naming module. In Terraform we had a locals.tf that computed resource names from environment, service, and region. The same logic existed, subtly different, in three places in our TypeScript app. In Pulumi we published an internal npm package:

// @internal/naming
export function resourceName(parts: {
  service: string;
  env: "dev" | "staging" | "prod";
  region: string;
  kind: string;
}): string {
  const { service, env, region, kind } = parts;
  return `${env}-${region}-${service}-${kind}`.toLowerCase();
}

export const commonTags = (service: string, env: string) => ({
  Service: service,
  Environment: env,
  ManagedBy: "pulumi",
  CostCenter: env === "prod" ? "product" : "engineering",
});

Both the app and the infra import from the same package. When we renamed an environment tag, one PR, one version bump, done. In Terraform this would have been a coordinated release across two repos and a fair bit of praying.

Testing

We wrote actual unit tests for our ECS module using Jest and Pulumi's mocks. Not integration tests — those still need real cloud calls — but structural assertions like "every service has a log group", "every task role has a specific boundary policy", "nothing in prod is tagged Environment: dev". These caught two real bugs in review before they ever hit a plan.

Terraform has terraform test now and it's improved, but writing assertions in HCL still feels like writing SQL to describe your feelings.

Dynamic composition

One ECS service had 14 near-identical sibling workers with per-worker config. In Terraform this was a for_each over a giant map with escape-hatch dynamic blocks. In Pulumi it's a for loop over a typed array with a class. Reviewing new worker additions went from "ugh" to "fine".

What We Rolled Back

After four months we moved the Vercel stack back to Terraform.

The reason was boring: the Pulumi Vercel provider we were using lagged behind the Terraform one on a specific feature we needed (project-level environment variable targeting by git branch). The Terraform provider had it; the Pulumi wrapper hadn't caught up. We could have contributed a patch, and we filed the issue, but we needed the feature that sprint.

This is the quiet reality of Pulumi in 2026: for AWS and GCP, the bridged providers are near-parity because they're literally wrapping the Terraform providers. For smaller ecosystems — Vercel, some SaaS providers, newer services — you can hit a lag of weeks or months. If your infra is 90% AWS, this rarely bites. If you live in a long tail of SaaS integrations, budget for it.

The Numbers, Framed Honestly

We don't want to invent benchmarks, so here are the shapes we saw over six months:

  • Onboarding time for new engineers to make a non-trivial infra change: roughly halved. This was the single biggest measurable improvement.
  • Plan/preview times: broadly similar. Pulumi felt slightly slower on cold runs, slightly faster on warm ones. Not a decision factor.
  • Incident count from infra changes: no meaningful change. Both tools will happily let you delete a database if you tell them to.
  • Lines of code: down about 30% on the migrated stacks, mostly from removing HCL boilerplate.
  • State-related pain: unchanged. State is state. Both tools require discipline.

When To Pick Which

This is what we tell clients now when the topic comes up on a DevOps engagement:

Stay on Terraform if:

  • Your team already knows HCL and isn't complaining.
  • You use a lot of niche providers.
  • Your infra team is separate from your product team and prefers a purpose-built DSL.
  • You rely heavily on the Terraform module registry ecosystem.

Consider Pulumi if:

  • Your infra people are also application people.
  • You want real testing, real types, and real package management.
  • You have complex dynamic composition that HCL fights you on.
  • You'd genuinely benefit from sharing code between app and infra.

Don't switch because:

  • HCL is "ugly". You'll trade one aesthetic complaint for another.
  • A vendor told you it's faster. Neither tool is meaningfully faster for realistic workloads.
  • You want to escape state management. You won't.

Where We'd Start

If you're seriously considering this in 2026, don't do a big-bang migration. Pick one greenfield stack and one painful existing stack. Run them in Pulumi for a full quarter with the same engineers who own the Terraform equivalents. Measure honestly: onboarding time, review time, incident count, developer sentiment. Then decide.

And whatever you do, draw a hard line between what each tool owns. Shared resources between Terraform and Pulumi are the fastest way to turn a clean migration into a two-year hybrid you can't escape from. If you want a second pair of eyes on that boundary, that's the kind of thing we help teams sort out on our infrastructure blog and in engagements — but you can absolutely do it yourselves with a weekend of honest whiteboarding first.

#Pulumi#Terraform#AWS#IaC#DevOps

Want a team like ours?

72Technologies builds production software for the kind of teams who actually read this blog.

Start a project