In 2026 the IaC landscape has three serious answers: Pulumi, Terraform, and OpenTofu. The HashiCorp BSL license decision in 2023 split the community; OpenTofu is the open-source fork. This post is the working comparison.

The core split

Terraform / OpenTofu uses HCL, a declarative configuration language. You write resource blocks; the tool diffs against state and applies the difference.

Pulumi uses real programming languages — Python, TypeScript, Go, .NET, Java, YAML. You write code that produces infrastructure. Same diff/apply model under the hood.

Both end up calling the same cloud APIs. The decision is about how you express intent.

Terraform / OpenTofu

# main.tf
resource "aws_s3_bucket" "data" {
  bucket = "my-app-data"
  tags = {
    Environment = "prod"
    Owner       = "team-data"
  }
}

resource "aws_s3_bucket_versioning" "data" {
  bucket = aws_s3_bucket.data.id
  versioning_configuration { status = "Enabled" }
}

variable "environments" {
  default = ["dev", "staging", "prod"]
}

resource "aws_s3_bucket" "logs" {
  for_each = toset(var.environments)
  bucket   = "logs-${each.key}"
}

What HCL is good at:

  • Declarative. Reads top-to-bottom; less imperative drift.
  • Single language across the team. Easier to onboard ops folks who don’t write code.
  • Provider ecosystem. ~3,000 providers covering virtually every cloud, SaaS, and tool.
  • Great VS Code / JetBrains integration with terraform-ls.

What HCL is painful at:

  • Loops, conditionals, functions. HCL has them; they read awkwardly.
  • Cross-module abstractions that share types and validation get complex.
  • Testing. Terratest works but isn’t loved.

Pulumi

// index.ts
import * as aws from "@pulumi/aws";

const data = new aws.s3.Bucket("data", {
  bucket: "my-app-data",
  tags: { Environment: "prod", Owner: "team-data" },
});

new aws.s3.BucketVersioning("data-versioning", {
  bucket: data.id,
  versioningConfiguration: { status: "Enabled" },
});

const environments = ["dev", "staging", "prod"];
for (const env of environments) {
  new aws.s3.Bucket(`logs-${env}`, { bucket: `logs-${env}` });
}

What Pulumi gives you:

  • Real loops, functions, packages. It’s TypeScript / Python / Go.
  • Typed configs. Refactor a tag schema; the compiler shows every place to update.
  • Native testing via your language’s test framework.
  • Shared abstractions through normal package management (npm, pip, go mod).
  • Same provider ecosystem. Pulumi vendors Terraform providers under the hood; you can use any of them.

What Pulumi costs:

  • Mixed languages on the team. A Pulumi monorepo with TS, Python, and Go modules creates onboarding friction.
  • Easier to make a mess. A real language gives you the rope to over-abstract.
  • State backend options were historically narrower (Pulumi Cloud or self-hosted S3). Better in 2026 but still less varied than Terraform.

OpenTofu

OpenTofu is a fork of Terraform 1.5 (the last MPL-licensed version) maintained by the Linux Foundation. Same HCL, same providers, same workflow. The diff is licensing and governance.

If you self-host Terraform OSS today, migrating to OpenTofu is a binary swap:

brew install opentofu/tap/opentofu
tofu init                 # same as terraform init
tofu plan
tofu apply

Some divergence is starting (encryption-at-rest for state, dynamic provider blocks, etc.) but they’re additive features. You can move freely.

If you depend on Terraform Cloud’s collaboration features, you have options: stay on HashiCorp’s offering, or migrate to Spacelift, env0, or Scalr — all support both Terraform and OpenTofu.

Decision matrix

PulumiTerraform / OpenTofu
Team prefers code
Team prefers declarative DSL
Cross-module abstractions⚠️
Testing⚠️
Onboarding ops folks⚠️
Provider breadth✅ (uses TF providers)
State backends✅ (Pulumi Cloud / self-host)✅ (many options)
Open-sourceTF: BSL; OpenTofu: MPL

For a typical 2026 platform team:

  • Devs-first culture, TypeScript/Python house: Pulumi.
  • Mixed ops + dev culture, declarative preference: Terraform or OpenTofu.
  • Self-host state, want pure OSS: OpenTofu.
  • Want HashiCorp’s hosted services: Terraform Cloud.

Either ecosystem produces working infrastructure. Don’t over-think it.

State management

Both tools maintain state files that map your code to live resources. This is the source of pain in IaC.

Best practices for both:

  • Remote state (S3 + DynamoDB locking, GCS, Azure Blob, Pulumi Cloud).
  • State per environment. Don’t co-mingle prod and dev state.
  • Lock state during operations so concurrent applies don’t collide.
  • Backup state daily.
  • Never edit state by hand unless you really know what you’re doing.

For platforms operating IaC at scale, see Platform Engineering and IDPs for how state fits into a larger IDP.

Provider extensions

Both tools support custom providers for SaaS APIs that aren’t covered. In 2026:

  • Terraform/OpenTofu providers are usually written in Go using the Terraform Plugin Framework.
  • Pulumi can wrap any Terraform provider via pulumi-tf-provider-bridge , and also supports native providers in Go/.NET.

If a provider exists for Terraform, you can use it from Pulumi. The reverse is also increasingly true.

Crossplane — the third option

Crossplane takes a different angle: infrastructure as Kubernetes Custom Resources. Continuously reconciles desired state against real state. No apply step.

  • Best for: Platform engineering teams that already run Kubernetes and want self-service infrastructure as Kubernetes objects.
  • Tradeoff: Different mental model. The platform owns Crossplane; app teams kubectl apply infrastructure.

Crossplane often complements Terraform/Pulumi rather than replacing them. Use Terraform/Pulumi for the K8s clusters and the foundations; use Crossplane for app-level resources (databases, queues) requested by application teams.

CI/CD patterns

A typical 2026 IaC pipeline:

on:
  pull_request:
    paths: ["infra/**"]

jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: opentofu/setup-opentofu@v1
      - run: tofu init
        working-directory: infra/${{ matrix.env }}
      - run: tofu plan -out=plan.bin
        working-directory: infra/${{ matrix.env }}
      - run: tofu show -json plan.bin > plan.json
      - uses: actions/upload-artifact@v4
        with: { path: infra/${{ matrix.env }}/plan.json }

  apply:
    if: github.ref == 'refs/heads/main'
    needs: [plan]
    environment: production              # required-reviewer gate
    runs-on: ubuntu-latest
    steps:
      - run: tofu apply plan.bin

Pattern: plan in CI on every PR, apply only on main with manual approval. Pulumi has analogous workflows via Pulumi Cloud or direct CLI in CI.

For supply chain security on your infrastructure, see Software Supply Chain Security in 2026 .

Common mistakes

1. Mixing imperative and declarative thinking

Both tools are declarative under the hood. If you find yourself reaching for “do this then that” in Pulumi, you’re fighting the model. Use stages or layered stacks instead.

2. One giant Terraform module

A 5,000-line main.tf is unmaintainable. Split by concern (network, IAM, services, data). Use terraform_remote_state (or Pulumi stack references) to compose.

3. State not locked

Two engineers running terraform apply simultaneously corrupts state. Always configure DynamoDB locking (or equivalent).

4. No cost guardrails

A wrong instance type costs $$$. Use tools like Infracost in CI to flag cost-changing PRs. Both Pulumi and Terraform support this.

5. Hand-editing state

terraform state rm and terraform import are powerful but dangerous. Document why you used them; review carefully. State surgery is the equivalent of git push --force.

What I’d pick today

For a brand-new team in 2026:

  • Greenfield Kubernetes / cloud-native: OpenTofu, with provider modules in a shared repo.
  • Devs-first house, lots of cross-resource logic: Pulumi (TypeScript or Python).
  • Internal developer platform with self-service infra: Crossplane on top of OpenTofu / Pulumi for the foundations.
  • Already on Terraform Cloud: stay; revisit if HashiCorp pricing changes.

Read this next

If you want my OpenTofu monorepo template (state backend, environments, secrets, Atlantis-style workflow), it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .