The IaC landscape shifted in 2024 when HashiCorp moved Terraform to BSL. OpenTofu (the OSS fork) matured fast. Pulumi continued growing on the “real programming language” angle. By 2026 the choice is clearer. This post is the working comparison.

The state in 2026

  • Terraform: BSL license; HashiCorp’s commercial product.
  • OpenTofu: MPL 2.0 fork of Terraform 1.5; community-driven; fully compatible with Terraform 1.5 syntax + its own forward features.
  • Pulumi: Apache 2.0; uses real programming languages (TS, Python, Go, .NET).
  • CDKTF: HashiCorp’s TS/Python/Go wrapper that synthesizes to Terraform JSON.

Most teams now run OpenTofu or Terraform Cloud + Terraform or Pulumi.

OpenTofu vs Terraform

# Identical for both:
resource "aws_s3_bucket" "logs" {
  bucket = "my-logs"
}

For 99% of HCL: identical. OpenTofu has added features:

  • Provider iteration (loops over providers).
  • State encryption built-in.
  • Improved import workflow.

If using Terraform 1.5+ HCL: OpenTofu reads it. Migration:

# Replace binary
brew uninstall terraform
brew install opentofu

# Same commands
tofu init
tofu plan
tofu apply

Existing state files work. CI workflows: replace terraform with tofu.

When to stay on Terraform

  • You use Terraform Cloud features (Sentinel policies, run tasks, etc.).
  • You have HashiCorp Enterprise contracts.
  • Your partners / vendors require Terraform specifically.

For everyone else: OpenTofu.

Pulumi

import * as aws from "@pulumi/aws";

const bucket = new aws.s3.Bucket("logs", {
    bucket: "my-logs",
    acl: "private",
});

export const bucketName = bucket.id;

Real TS. Real loops. Real conditionals. Real testing.

const env = pulumi.getStack();
const sizes = env === "prod" ? 5 : 1;

for (let i = 0; i < sizes; i++) {
    new aws.ec2.Instance(`worker-${i}`, { ... });
}

In HCL this is awkward (count, for_each); in Pulumi it’s just a loop.

Pulumi vs Terraform/OpenTofu

Terraform/OpenTofuPulumi
LanguageHCLTS / Python / Go / .NET
Provider countMost providersMost providers
State backendManyMany + Pulumi Cloud
Loops / conditionalsAwkwardNative
Testingterratest / unitBuilt-in unit + integration
ModulesHCL modulesComponents (real classes)
HiringMassive poolSmaller but growing
Learning curveLow (declarative)Higher (real code)

For TS/Python shops: Pulumi is genuinely productive. For “we want devs to learn one thing”: HCL is simpler.

Migration: Terraform → OpenTofu

# 1. Test on a non-critical workspace
tofu init -upgrade
tofu plan  # should match terraform plan output

# 2. Update CI
# Replace `terraform` invocation with `tofu`

# 3. Move state
# State format identical; just point tofu at the same backend.

# 4. Done

Most teams complete this in a week.

Migration: Terraform → Pulumi

pulumi import-from terraform <state-file>

Generates Pulumi code from existing Terraform state. Manual review needed; ergonomic but not perfect.

For mostly-static infra, this works well. For heavily-modulized Terraform: more work.

State

All three back state similarly:

  • S3 + DynamoDB lock (most common).
  • GCS with native locking.
  • Azure storage.
  • Terraform Cloud / Pulumi Cloud / Spacelift / Env0 (managed).

For OSS: S3 + DynamoDB or Pulumi self-hosted backend.

Modules

# Terraform
module "vpc" {
  source = "terraform-aws-modules/vpc/aws"
  version = "5.5.0"
  ...
}
// Pulumi
import { Vpc } from "./components/vpc";
new Vpc("main", { cidr: "10.0.0.0/16" });

Terraform modules: registry has thousands. Pulumi components: build your own + a smaller registry.

Drift detection

tofu plan
# Reports drift between state and actual cloud state

Or run via Atlantis / Spacelift / Env0 on every PR + scheduled.

For Pulumi: pulumi refresh + pulumi preview.

Tools that work with all

  • Atlantis: PR automation.
  • Spacelift: managed GitOps for IaC.
  • Env0: similar.
  • Terragrunt: keeps existing if you used it for TF.

Most workflows portable.

Common mistakes

1. Mixing Terraform and OpenTofu in one team

Confusing; subtle differences. Pick one.

2. State file in git

Plaintext; can contain secrets. Always remote backend with locking.

3. No CI lock

Two devs run apply at once → corruption. Always use locking.

4. Pulumi without abstractions

Treating it as “Terraform with TS.” You’re missing the point — build real classes.

5. Drift-and-pray

Manual changes in console; IaC slowly diverges. Detect drift in CI.

What I’d ship today

For a new team:

  • OpenTofu for declarative IaC; modules from registry; CI via Atlantis.
  • Pulumi if the team is TS / Python first, wants tests, and is comfortable with code-as-config.
  • Stay on Terraform only if Terraform Cloud features matter.

For an existing Terraform shop: migrate to OpenTofu. The cost is low; the freedom is real.

Read this next

If you want my OpenTofu starter (modules, state backend, CI), 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 .