Arvind Verma

Terraform vs OpenTofu vs Pulumi in 2026: which to choose

Terraform, OpenTofu and Pulumi compared in November 2026: licences, current versions, state encryption, Stacks, pricing, migration commands and what to pick.

By Arvind Verma13 min read

In short

  • Terraform 1.17 is BSL-licensed and owned by IBM. OpenTofu 1.13 is MPL 2.0 under the Linux Foundation. Both run the same HCL against the same providers.
  • OpenTofu adds state encryption and variables in backend blocks. Terraform adds search, actions, policy and Stacks, and Stacks need HCP Terraform.
  • Pulumi suits teams who want TypeScript, Python or Go, and can use any Terraform provider. CDK for Terraform was deprecated on 10 December 2025.
On this page
  1. The short answer
  2. How the licences got here
  3. Current versions
  4. Terraform and OpenTofu: what differs in practice
  5. Terraform Stacks
  6. HCP Terraform pricing
  7. Pulumi
  8. CDK for Terraform is gone
  9. Comparison
  10. State management and CI patterns
  11. Migration paths
  12. What I’d pick
  13. Migration checklist

This is for teams choosing an infrastructure-as-code tool, or wondering whether their Terraform estate should move. It covers the licence history, current versions, the features that differ, hosted pricing, migration commands, and what I would pick in three common situations. Every version, price and date was checked on 13 November 2026 against the projects’ own release notes, docs and pricing pages.

I have used Terraform and Ansible in client work, and I run production systems on AWS, Azure, Google Cloud and Oracle Cloud. The opinions are mine; the facts come from the sources at the end.

The short answer

  • Small team starting fresh, happy with HCL: OpenTofu, with encrypted state in your own object storage.
  • Existing Terraform estate: stay if you use HCP Terraform features such as Stacks; otherwise OpenTofu is a cheap, reversible switch.
  • Developer-heavy team: Pulumi. If you are on CDK for Terraform, plan your exit now.

How the licences got here

Date Event
10 August 2023 HashiCorp moves future releases from MPL 2.0 to the Business Source License 1.1 (BSL). APIs, SDKs and “almost all other libraries” stay MPL 2.0.
20 September 2023 The Linux Foundation announces OpenTofu, previously OpenTF, a fork of MPL-licensed Terraform.
4 October 2023 Terraform 1.6.0, the first version under the BSL.
10 January 2024 OpenTofu 1.6.0, its first stable release.
24 April 2024 IBM agrees to buy HashiCorp for $35 a share in cash, an enterprise value of $6.4 billion.
27 February 2025 IBM completes the acquisition.
23 April 2025 OpenTofu is accepted into the CNCF at Sandbox level.

Today Terraform’s licence file names International Business Machines Corporation as licensor, HashiCorp’s pricing page sells “IBM HCP Terraform”, and the licence is still BSL 1.1. Its Additional Use Grant allows production use unless you offer Terraform to third parties, hosted or embedded, in a paid product competing with IBM’s paid versions. Internal use is explicitly allowed, so it matters only if you sell a product or managed service built on Terraform.

Each version converts to MPL 2.0 four years after publication, so 1.6.0 becomes MPL 2.0 in October 2027. Providers are separate projects: the AWS provider is still MPL 2.0, which is why OpenTofu and Pulumi can use it. OpenTofu is MPL 2.0; Pulumi’s engine and CLI are Apache 2.0.

Current versions

Tool Latest stable Released Notes
Terraform 1.17.2 11 November 2026 1.17.0 shipped 21 October 2026; the 1.16 line still gets patches (1.16.7)
OpenTofu 1.13.9 5 November 2026 1.12.12 shipped the same day; 1.13 has security support until August 2027
Pulumi CLI 3.276.0 12 November 2026 A new minor release most weeks

Terraform and OpenTofu: what differs in practice

They share the language, the providers, the plan and apply workflow and, at the fork, the state format. You type tofu instead of terraform. Since 2023 several features have landed in both, at different versions.

Feature Terraform OpenTofu
Client-side state and plan encryption No; the docs rely on the backend encrypting at rest, plus Hold Your Own Key in HCP Terraform Yes, since 1.7
Variables in backend blocks No: a backend “cannot refer to named values” Yes, since 1.8, resolved at init
Variables in module source and version 1.15, via const variables 1.8
Provider-defined functions (provider::<name>::<function>) 1.8, 10 April 2024 1.7, 30 April 2024, plus functions defined dynamically from configuration
Deprecated variables and outputs 1.15 1.10
Ephemeral values 1.10 (ephemeral variables) 1.11 (ephemeral resources and write-only attributes)
destroy = false in lifecycle 1.16 1.12
S3 native locking (use_lockfile) Yes; DynamoDB locking deprecated Yes; DynamoDB locking “fully supported”, no plans to deprecate
Test framework terraform test, mocks since 1.7 tofu test, mocks since 1.8
Default registry registry.terraform.io registry.opentofu.org

OpenTofu’s state encryption

State holds every attribute of every resource, including passwords that providers return. Terraform’s docs call local state “a plaintext file” and say encryption at rest “depends on your specific backend”. OpenTofu encrypts state and plan files on the client, with PBKDF2, AWS KMS, GCP KMS, Azure Key Vault, OpenBao or external key providers and AES-GCM.

For an existing project, the documented migration adds an unencrypted fallback so the old state can be read once:

terraform {
  encryption {
    method "unencrypted" "migrate" {}

    key_provider "aws_kms" "state" {
      kms_key_id = "a4f791e1-0d46-4c8e-b489-917e0bec05ef"
      region     = "us-east-1"
      key_spec   = "AES_256"
    }

    method "aes_gcm" "state" {
      keys = key_provider.aws_kms.state
    }

    state {
      method = method.aes_gcm.state

      fallback {
        method = method.unencrypted.migrate
      }
    }
  }
}

Run tofu apply, then remove the fallback block and set enforced = true inside state so plaintext can never be written again. Plans are only encrypted if you add a plan {} block. In CI, the same configuration can come from the TF_ENCRYPTION environment variable.

The docs’ warnings matter: lose the key and you lose the state, never rename key providers or methods after encrypting, and use a separate KMS key per state file. Terraform cannot read encrypted state, so this step ends easy rollback.

Variables in backend blocks

OpenTofu 1.8 (29 July 2024) allowed variables and locals in module sources, backend configuration and encryption blocks, so region = local.region works inside backend "s3". Values must resolve during tofu init, before state exists, and the docs advise against secrets there. Terraform has closed part of this gap: 1.15 (29 April 2026) added const variables for module source and version, and 1.17 allows variables and locals in provider requirements. Backend blocks still cannot use them, so in Terraform you keep using -backend-config files.

What Terraform has that OpenTofu does not

  • Stacks, covered below, in HCP Terraform only.
  • List resources and terraform query (1.14, 19 November 2025): *.tfquery.hcl files that search existing infrastructure and can generate import configuration.
  • Actions (1.14): provider-defined operations outside create, read, update and delete.
  • Terraform Policy, generally available in 1.17 through the -policies flag.
  • HCP Terraform and Terraform Enterprise, which run Terraform. Moving to OpenTofu means moving runs to your own CI or another platform.

I found no equivalent of these in OpenTofu’s 1.10 to 1.13 release notes.

What OpenTofu has that Terraform does not

  • State encryption and backend variables, above.
  • .tofu files. When main.tofu and main.tf both exist, OpenTofu ignores the .tf file, so module authors can ship OpenTofu-only features and stay Terraform-compatible.
  • OCI registry distribution of providers and modules (1.10), aimed at air-gapped environments.
  • The enabled meta-argument (1.11), inside lifecycle, for resources that exist zero or one times.

I could not confirm whether Terraform has equivalents of the last two.

Registries

hashicorp/aws means registry.terraform.io/hashicorp/aws in Terraform and registry.opentofu.org/hashicorp/aws in OpenTofu. The OpenTofu registry reviews submissions made through GitHub issue forms and treats published versions as immutable. Pulumi also fetches Terraform providers from it by default.

Testing

Both tools run *.tftest.hcl files. This example from Terraform’s docs works under terraform test or tofu test:

# valid_string_concat.tftest.hcl

variables {
  bucket_prefix = "test"
}

run "valid_string_concat" {
  command = plan

  assert {
    condition     = aws_s3_bucket.bucket.bucket == "test-bucket"
    error_message = "S3 bucket name did not match expected"
  }
}

The default command is apply, which builds real infrastructure; plan keeps it to logic checks. Add mock_provider "aws" {} to test without credentials. OpenTofu also reads *.tofutest.hcl, which takes precedence over a .tftest.hcl of the same name.

Terraform Stacks

Stacks became generally available on 25 September 2025 for HCP Terraform plans based on resources under management. A Stack groups modules as components (*.tfcomponent.hcl) and repeats them as deployments (*.tfdeploy.hcl), one per environment, account or region, each with its own state. Limits are 20 deployments, 100 components and 10,000 resources per Stack, and legacy team plans cannot use them.

Stacks replace what many teams do with a directory per environment and a wrapper tool. The docs describe them only inside HCP Terraform, and I could not confirm Terraform Enterprise support, so adopting Stacks means adopting HCP Terraform.

HCP Terraform pricing

HCP Terraform bills by managed resource:

Plan Price Notes
Free $0 500 managed resources; unlimited users, but no teams; 1 concurrent remote run and 1 agent run
Essentials From $0.10 per resource a month ($0.00013 an hour) Pay as you go
Standard From $0.47 per resource a month ($0.00064 an hour) Through sales
Premium From $0.99 per resource a month ($0.00135 an hour) Through sales
Terraform Enterprise Custom Self-managed

Billing is hourly on the peak count. null_resource, terraform_data and data sources are not counted. The legacy free plan ended on 31 March 2026, and remaining organisations moved to the 500-resource tier. The pricing page does not say whether the first 500 stay free on a paid plan, so budget as if every resource is billed: 3,000 resources on Essentials is about $300 a month at list price. If you are trimming cloud spend too, see my AWS cost optimisation checklist.

Pulumi

Pulumi programs are written in TypeScript, Python, Go, .NET, Java, YAML or HCL. The program declares the desired state; pulumi preview shows the diff and pulumi up applies it. Loops, types, packages and unit tests come from the language.

Terraform providers. Any Terraform or OpenTofu provider can be used, with a typed SDK generated in your language:

pulumi package add terraform-provider hashicorp/random 3.7.1

HCL. With runtime: hcl in Pulumi.yaml (CLI 3.256.0 or later), Pulumi runs .tf files on its own engine. It ignores backend and required_version, treats a cloud block as an error, and does not read Terraform state; existing resources come in with pulumi import --from hcl <statefile>.

State. Pulumi Cloud by default, or a DIY backend with file-based locking, such as pulumi login s3://<bucket-name> (also azblob://, gs:// and --local). DIY backends lack Pulumi Cloud’s audit, drift detection and recovery from some partial failures. Pulumi Cloud can also be a state backend for Terraform and OpenTofu (hostname = "tf.pulumi.com" in a backend "remote" block), where each Terraform resource counts as a managed resource.

Testing. Unit tests run in memory with mocks in your language’s usual framework. Property tests use Pulumi policies (Node.js and Python). Integration tests deploy, check and destroy real infrastructure.

Pulumi ESC manages environments, configuration and secrets. It pulls from AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, HashiCorp Vault and 1Password, issues short-lived cloud credentials, and works with applications and pulumi env run, not only Pulumi programs.

Pulumi Cloud pricing

Edition Price What you get
Free $0 1 user, no managed resource limit, 500 workflow minutes a month, 25 ESC secrets, 10,000 ESC API calls a month
Essentials $40 a month, including 40 credits Up to 500 managed resources; unlimited users; resources at $0.00025 an hour (about $0.18 a month)
Pro $400 a month, including 400 credits Up to 2,000 managed resources; SAML SSO, RBAC, audit logs, drift detection; resources from $0.0005 an hour
Enterprise Custom Self-hosting available

One credit is $1. Usage beyond the pool is billed in arrears, and unused credits expire monthly. On Essentials, ESC secrets cost $0.50 a month each. The single-user limit is what matters: a team of two pays, or runs a DIY backend.

CDK for Terraform is gone

HashiCorp deprecated CDK for Terraform on 10 December 2025 and archived the repository the same day; there will be no further fixes. The README offers two exits: cdktf synth --hcl to generate Terraform-compatible .tf files, or AWS CDK if you are already built around it. Pulumi’s migration docs start from that same synthesised HCL, and for teams that chose CDKTF for a general-purpose language, Pulumi is the closest match.

Comparison

Terraform OpenTofu Pulumi
Licence BSL 1.1, IBM MPL 2.0 Apache 2.0 (engine and CLI)
Governance IBM (HashiCorp) Linux Foundation; CNCF Sandbox Pulumi
Latest stable 1.17.2 1.13.9 3.276.0
Language HCL HCL TypeScript, Python, Go, .NET, Java, YAML, HCL
Providers Terraform registry OpenTofu registry, same providers Native providers plus any Terraform provider
State encryption Backend at rest; HYOK in HCP Terraform Built in, client side Secrets encrypted; customisable provider
Testing terraform test tofu test Your language’s test framework
Hosted service HCP Terraform, free to 500 resources None from the project Pulumi Cloud, free for 1 user

State management and CI patterns

The same rules apply to all three.

  • Remote state with locking. For S3, set use_lockfile = true; Terraform and OpenTofu both support it.
  • One state per environment and blast radius, so a plan never touches everything.
  • Plan on pull requests, apply on merge. HashiCorp’s automation guide uses terraform init -input=false, terraform plan -out=tfplan -input=false and terraform apply -input=false tfplan. A saved plan contains configuration, state and variables, so protect it, and provider versions must match between plan and apply.
  • No stored cloud keys. Use OIDC; my CI/CD tools comparison has the AWS trust policy.

This GitHub Actions workflow plans pull requests with a read-only role and applies main with a separate one:

name: infra

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read
  id-token: write

jobs:
  plan:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: opentofu/setup-opentofu@v2
        with:
          tofu_version: 1.13.9
      - uses: aws-actions/configure-aws-credentials@v6
        with:
          role-to-assume: arn:aws:iam::123456789012:role/infra-plan
          aws-region: eu-west-2
      - run: tofu init -input=false
      - run: tofu plan -input=false

  apply:
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    environment: production
    concurrency:
      group: infra-production
      cancel-in-progress: false
    steps:
      - uses: actions/checkout@v7
      - uses: opentofu/setup-opentofu@v2
        with:
          tofu_version: 1.13.9
      - uses: aws-actions/configure-aws-credentials@v6
        with:
          role-to-assume: arn:aws:iam::123456789012:role/infra-apply
          aws-region: eu-west-2
      - run: tofu init -input=false
      - run: tofu plan -input=false -out=tfplan
      - run: tofu apply -input=false tfplan

For Terraform, use hashicorp/setup-terraform@v4 with terraform_version: 1.17.2 and run terraform instead of tofu. Pull request jobs and jobs in the production environment present different OIDC subjects, so write each role’s trust policy for its own job.

Migration paths

Terraform to OpenTofu

OpenTofu’s guide calls the process “safe and reversible”:

  1. Back up state (local files, or your backend’s backups such as S3 versioning) and commit configuration on a migration branch.
  2. Install OpenTofu and check it with tofu --version.
  3. Run tofu init. Providers now come from the OpenTofu registry; review the lock file changes.
  4. Run tofu plan. You should see “No changes” or the plan Terraform would produce. If not, stop.
  5. Run tofu apply. This can update the state format even with no infrastructure changes.
  6. Make a small change, such as a tag, and plan and apply it.

To roll back, restore state from backup if it changed, then run terraform init and terraform plan.

If configurations share data through terraform_remote_state, migrate the ones that read state before the ones they read from. OpenTofu reads Terraform 1.x state without modification, but “Terraform may not reliably read OpenTofu state files”, so avoid OpenTofu-only features until everything has moved.

Also check cloud blocks, WinRM provisioners (removed in OpenTofu 1.13) and required_version, which older OpenTofu versions treated as an OpenTofu constraint. The documented fix is a versions.tofu file, read instead of versions.tf, holding language { compatible_with { opentofu = ">= 1.12" } }.

Terraform to Pulumi

There are four documented routes, which you can combine.

Convert the code. From the directory with your .tf files:

pulumi convert --from terraform --language typescript --out ../pulumi-infra

Python, Go and C# also work. Modules become components; anything unsupported becomes a notImplemented stub to finish by hand.

Import the state. Pull remote state to a file, then import it into a Pulumi stack. Imported resources are marked protected:

terraform state pull > terraform.tfstate
pulumi import --from terraform ./terraform.tfstate

Translate state first. pulumi plugin run terraform-migrate -- stack (with --from, --to, --out and --plugins) converts Terraform state into a file for pulumi stack import. It needs tofu on your PATH and only runs tofu init and tofu refresh, never an apply.

Coexist. Pulumi programs can read Terraform outputs with terraform.state.getRemoteReference or getLocalReference, so new services can start in Pulumi while old ones stay put.

Whichever route you take, check that pulumi preview shows no unexpected changes to existing resources before you rely on it.

What I’d pick

A small team starting fresh. OpenTofu, if the team is comfortable with HCL: an open licence, the same providers and modules, client-side state encryption and no per-resource bill. Keep state in object storage with locking, encrypt it with a KMS key from day one, and run plans from CI with OIDC. If you would rather not build that pipeline, HCP Terraform’s free tier is a real offer, but price the step after 500 resources first.

An existing Terraform estate. If you use, or plan to use, Stacks, policy or HYOK on HCP Terraform, stay and budget for managed resources. If you run the CLI in your own CI with state in a bucket, OpenTofu is a cheap change you can reverse, provided you hold off on encryption and .tofu files until you have decided. If you sell a hosted product built on Terraform, read the Additional Use Grant with your lawyer.

A developer-heavy team. Pulumi, in the language the team already uses. Infrastructure gets the same reviews, tests and packaging as application code, Terraform providers stay available, and ESC handles secrets. CDKTF users should move here or back to HCL. Budget for Essentials or a DIY backend once two people deploy, and review the code strictly: a general-purpose language makes clever code easy.

If you use AI agents on infrastructure code, HashiCorp’s Terraform MCP server is covered in MCP servers for DevOps engineers.

Migration checklist

  • Inventory every state: backend, managed resource count, tool and provider versions, terraform_remote_state links, cloud blocks, provisioners.
  • Turn on versioning for every state bucket and take a backup before any change.
  • Upgrade all configurations to the same Terraform version first.
  • Migrate one low-risk configuration on a branch, readers before the states they read.
  • Require “No changes” from tofu plan or pulumi preview before the first apply.
  • Update CI setup steps, pin tool versions and keep OIDC credentials.
  • Enable state encryption only once stable: a KMS key per state, keys backed up, fallback for the switch, then enforced = true.
  • Keep backups and the old tool version until several weeks pass without surprises.
  • On CDKTF, run cdktf synth --hcl now and choose between HCL and Pulumi.

Sources

  1. HashiCorp adopts Business Source License (HashiCorp blog) hashicorp.com
  2. Terraform LICENSE (GitHub) github.com
  3. Terraform AWS provider LICENSE (GitHub) github.com
  4. Announcing OpenTofu (Linux Foundation) linuxfoundation.org
  5. OpenTofu LICENSE (GitHub) github.com
  6. OpenTofu project page (CNCF) cncf.io
  7. IBM to acquire HashiCorp (IBM Newsroom) newsroom.ibm.com
  8. IBM completes acquisition of HashiCorp (IBM Newsroom) newsroom.ibm.com
  9. Pulumi LICENSE (GitHub) github.com
  10. Terraform releases (GitHub) github.com
  11. Terraform v1.17 changelog (GitHub) github.com
  12. Terraform v1.16 changelog (GitHub) github.com
  13. Terraform v1.15 changelog (GitHub) github.com
  14. Terraform v1.14 changelog (GitHub) github.com
  15. OpenTofu releases (GitHub) github.com
  16. OpenTofu v1.13 changelog (GitHub) github.com
  17. OpenTofu v1.13.0 (OpenTofu blog) opentofu.org
  18. OpenTofu v1.12.0 (OpenTofu blog) opentofu.org
  19. OpenTofu v1.11.0 (OpenTofu blog) opentofu.org
  20. OpenTofu v1.10.0 (OpenTofu blog) opentofu.org
  21. OpenTofu v1.8.0 (OpenTofu blog) opentofu.org
  22. OpenTofu v1.7.0 (OpenTofu blog) opentofu.org
  23. State and plan encryption (OpenTofu docs) opentofu.org
  24. Backend configuration (OpenTofu docs) opentofu.org
  25. Input variables (OpenTofu docs) opentofu.org
  26. OpenTofu settings (OpenTofu docs) opentofu.org
  27. Provider requirements (OpenTofu docs) opentofu.org
  28. S3 backend (OpenTofu docs) opentofu.org
  29. Command: test (OpenTofu docs) opentofu.org
  30. Migration guide (OpenTofu docs) opentofu.org
  31. Migrating interdependent configurations (OpenTofu docs) opentofu.org
  32. opentofu/registry (GitHub) github.com
  33. opentofu/setup-opentofu (GitHub) github.com
  34. Backend block configuration (Terraform docs) developer.hashicorp.com
  35. Variable block reference (Terraform docs) developer.hashicorp.com
  36. S3 backend (Terraform docs) developer.hashicorp.com
  37. Sensitive data in state (Terraform docs) developer.hashicorp.com
  38. Provider requirements (Terraform docs) developer.hashicorp.com
  39. Functions (Terraform docs) developer.hashicorp.com
  40. Terraform 1.8 improves extensibility with provider-defined functions (HashiCorp blog) hashicorp.com
  41. Terraform 1.8 provider functions for AWS, Google Cloud, and Kubernetes (HashiCorp blog) hashicorp.com
  42. Tests (Terraform docs) developer.hashicorp.com
  43. Mocks (Terraform docs) developer.hashicorp.com
  44. Stacks (Terraform docs) developer.hashicorp.com
  45. Scale infrastructure with new Terraform and Packer features at HashiConf 2025 (HashiCorp blog) hashicorp.com
  46. HashiCorp pricing hashicorp.com
  47. HCP Terraform plans and features (Terraform docs) developer.hashicorp.com
  48. Continuing HCP Terraform's enhanced free tier experience (HashiCorp blog) hashicorp.com
  49. Running Terraform in automation (HashiCorp tutorial) developer.hashicorp.com
  50. hashicorp/setup-terraform (GitHub) github.com
  51. hashicorp/terraform-cdk (GitHub) github.com
  52. CDK for Terraform (Terraform docs) developer.hashicorp.com
  53. Pulumi releases (GitHub) github.com
  54. Pulumi pricing pulumi.com
  55. Migrating from Terraform to Pulumi (Pulumi docs) pulumi.com
  56. pulumi convert (Pulumi CLI docs) pulumi.com
  57. Using any Terraform provider (Pulumi docs) pulumi.com
  58. HCL (Pulumi docs) pulumi.com
  59. Terraform state backend (Pulumi docs) pulumi.com
  60. Pulumi ESC (Pulumi docs) pulumi.com
  61. State and backends (Pulumi docs) pulumi.com
  62. Using a DIY backend (Pulumi docs) pulumi.com
  63. Testing Pulumi programs (Pulumi docs) pulumi.com
  64. actions/checkout releases (GitHub) github.com
  65. aws-actions/configure-aws-credentials releases (GitHub) github.com