Search nomadLab

Terraform, OpenTofu, Pulumi: The Fork Is in the Registry

The BUSL has an expiry date printed in it, four years per version, and IBM is the licensor. The split that actually changes what you can build is which registry your source address resolves to, and 3,198 provider names sit on only one side of it.

Updated

Most comparisons of these three open with the license and then spend the rest of the article on syntax. The license is the easy part, because the terms are written down with dates attached. What is harder to see, and what decides more of your day, is that the three tools no longer read from the same place and no longer store your state the same way.

Start with the file everyone argues about. LICENSE in the Terraform repository is Business Source License 1.1, and the Licensor line now reads International Business Machines Corporation. The Additional Use Grant permits production use as long as you are not offering Terraform to third parties on a hosted or embedded basis in competition with IBM’s paid versions, and it says plainly that “hosting or using the Licensed Work(s) for internal purposes within an organization is not considered a competitive offering.” If you run terraform apply in your own pipeline against your own cloud account, the BUSL has never applied to you and still does not.

The part that gets skipped is the two lines under it. Change Date: four years from the date the Licensed Work is published. Change License: MPL 2.0. The BUSL is not permanent, and it expires per version rather than all at once.

A timeline from 2024 to 2030 showing that each Terraform version under the Business Source License converts to MPL 2.0 four years after it was published. Terraform 1.6.0, published 4 October 2023, becomes MPL 2.0 on 4 October 2027. Terraform 1.15.0, published 29 April 2026, becomes MPL 2.0 on 29 April 2030. OpenTofu 1.6.0 and everything after it was MPL 2.0 from the day it shipped in January 2024. The BUSL expires one version at a time. Four years, each. Terraform 1.6.0, published 4 Oct 2023 MPL 2.0 on 4 Oct 2027 Terraform 1.15.0, published 29 Apr 2026 MPL 2.0 on 29 Apr 2030 OpenTofu 1.6.0 onward, shipped 10 Jan 2024 MPL 2.0 from day one 2024 2026 2028 2030
Terraform 1.5.7, released 7 September 2023, was the last MPL build and the point OpenTofu forked from. Every BUSL release since carries its own four-year clock.

That clock matters for one specific kind of buyer: a company whose legal team wants a date it can put in a risk register. It matters much less if you were going to upgrade Terraform twice a year anyway, because the version you run will always be the one still inside its four years.

The registry is where the fork actually happened

Providers are the part you cannot fork your way around, and this is where the two tools stopped being interchangeable. OpenTofu ships its own registry and uses it by default. In a required_providers block, a source address with no hostname “defaults to registry.opentofu.org” under tofu and to registry.terraform.io under terraform. Same HCL, same lock file format, different origin server.

I counted both catalogs on 23 August 2026. The Terraform Registry API reports 7,109 providers. The opentofu/registry repository that feeds OpenTofu’s registry carries 4,536 provider entries, and 3,198 of the Terraform Registry’s names have no entry in it.

That gap is smaller than it sounds and less reliable than it sounds, both for the same reason. Sorted by download count, 99 of the Terraform Registry’s top 100 providers have an entry in OpenTofu’s index, and the one that does not, yandex-cloud/yandex, still answers when you ask the registry API for its versions. Going the other way, I spot-checked eight long-tail providers that the index omits: seven returned 404 and one resolved anyway.

So the head is covered and the tail is where you find out. Do not trust either count, including mine. Ask the API about the providers you actually use:

grep -rhoE '^\s+source\s*=\s*"[a-z0-9-]+/[a-z0-9._-]+/?[a-z0-9._-]*"' \
  --include='*.tf' . | grep -oE '"[^"]+"' | tr -d '"' | sort -u \
  | while read -r p; do
      code=$(curl -s -o /dev/null -w '%{http_code}' \
        "https://registry.opentofu.org/v1/providers/$p/versions")
      echo "$code $p"
      sleep 1
    done

Keep it sequential. The registry throttles concurrent clients with 403s, which look exactly like a missing provider if you are not reading closely. Anything that comes back 404 after a retry is a provider you would have to mirror, vendor into a private registry, or stay on Terraform for. That loop answers the migration question better than any comparison table, mine included.

Pulumi sits on the same providers from a third angle. Its registry API lists 355 packages: 43 native, 296 bridged from a Terraform-style provider, and 8 components. For 106 of the bridged packages the registry names the source as opentofu. Pulumi’s answer to “do you support provider X” is a bridge over the same provider binaries the other two consume, which is why the coverage question for Pulumi is not “is there a Pulumi provider” but “does the bridge translate this provider cleanly.”

Three answers to “who can read your state”

State is the other place the three genuinely differ, and it is the difference with the shortest path to a security review.

Terraform does not encrypt state itself. Its own documentation is direct about it: Terraform “stores your state in a plaintext file, which includes any secret values you defined in your configuration,” and values marked sensitive land “in both state and plan files, and anyone who can access those files can access your sensitive values.” The recommended protection is backend encryption, meaning S3 with encrypt, a customer-managed key on GCS, or HCP Terraform. All of that protects the bucket. None of it stops someone who can read the bucket.

OpenTofu encrypts the file before it leaves your machine. State and plan encryption uses AES-GCM with keys from PBKDF2, AWS KMS, GCP KMS, Azure Key Vault, OpenBao, or an external program. Plan files get the same treatment through the same config block. The warning in that page is the one to read twice: once the data is encrypted, OpenTofu cannot read the state without the correct key. You have moved a bucket-permissions problem into a key-custody problem, which is an upgrade only if you already know how to hold keys.

Pulumi splits the difference. Individual secret values are encrypted inside an otherwise readable state file, with per-stack keys from Pulumi Cloud by default and awskms, azurekeyvault, gcpkms, or hashivault if you want to hold the key yourself. Resource names, IDs, and structure stay legible. Whether that is a feature depends on whether the thing you are hiding is a password or the shape of your network.

State at restWhat is encryptedKey custody
TerraformBackend’s jobNothing, by the toolCloud provider or HCP
OpenTofuClient side, AES-GCMWhole state and plan filesYou, via KMS, OpenBao, or a passphrase
PulumiMixedValues marked secretPulumi Cloud by default, KMS or Vault optional

HCL or a language you already have

This part has not changed since 2023 and does not need another thousand words. Terraform and OpenTofu take HCL, which is declarative and deliberately short on escape hatches. Pulumi takes TypeScript, Python, Go, Java, C#, or YAML, and gives you the loops, conditionals, and package manager that come with them.

The honest trade is about testing rather than expressiveness. A Pulumi program is a program, so it can be unit tested with whatever test runner your team already runs, in memory, without touching a cloud account. HCL’s equivalent is tofu test and terraform test, which run assertions written in HCL and are a real improvement on validate and plan, but they are still assertions in a configuration language. If your platform team writes application code all week, that difference compounds. If they do not, HCL’s smaller surface is a feature and Pulumi’s package.json is a tax.

Cost is a fourth axis and mostly separate from the tool. The meter is in the platform you run it on, not the binary, and the free tier that used to make this decision for small teams is gone; I wrote about what HCP Terraform and its competitors count after that change.

Where each one is the correct answer

Stay on Terraform if you are already there, your providers all come from the Terraform Registry, and nobody has asked you to put a license risk on a register. The BUSL grant covers internal use, the registry is the larger catalog, and moving costs real time for a benefit you may not need.

Move to OpenTofu if the license question comes from someone with authority to block you, or if you want state encrypted before it hits the bucket. Run the provider check above first. The migration is tofu init against existing state and a CI change, and going back becomes awkward once newer state metadata is written, so treat the first tofu apply as the point of commitment and take a backup of the state files before it.

Choose Pulumi if you want infrastructure tested the way your services are tested, and check the bridge coverage for your two or three least popular providers before you convert anything.

The version of this decision that ages well is not “which tool wins.” It is a list you can regenerate: which providers you actually use, which registries serve them, and who holds the key to the file that knows all your resource IDs. Run the loop above against your own repository and you will know more than a comparison table can tell you.

Keep reading