Brief History
- Aug 2023. HashiCorp relicenses Terraform from MPL-2.0 to BSL (Business Source License), which prohibits commercial alternatives.
- Sep 2023. The Linux Foundation announces OpenTofu, a fork of the last MPL release (1.5.x).
- 2024. OpenTofu 1.6 and 1.7 ship; features accumulate in parallel with Terraform, sometimes ahead of it.
- 2025. Both tools are active; community providers support both.
At 1.5 the two are technically identical. From 1.6 onward, divergence grows.
What Is the Same
- HCL syntax.
- All providers (single source, Registry).
- Commands: init, plan, apply, destroy, fmt, validate.
- State format and backward compatibility. Migration from
terraform statetotofu staterequires no conversion. - Backends (S3, GCS, local, Terraform Cloud in read-only mode for OpenTofu).
- The 95% of everyday use cases.
What Is Different
| Feature | Terraform | OpenTofu |
|---|---|---|
*.tftest.hcl framework | 1.6+ | 1.6+ |
| mock_provider | 1.7+ | 1.7+ |
| state encryption at rest | No (backend SSE only) | Yes (native, since 1.7) |
exclude in for_each | No | Yes (since 1.8) |
| Stacks | Yes (HCP-only) | No |
| Terraform Cloud integration | Native | Read-only (via OAuth) |
| License | BSL | MPL-2.0 |
| Provider registry | registry.terraform.io | registry.opentofu.org (mirror) |
Migration
Scenario: you are using an HCP-free workflow and want to move away from BSL.
# Terraform was installed like this:
brew install hashicorp/tap/terraform
# OpenTofu:
brew install opentofu
tofu --version
From that point on, use tofu in place of terraform:
tofu init
tofu plan
tofu apply
State is compatible. Running tofu plan against a raw terraform.tfstate
produced by Terraform works without conversion. The lockfile
(.terraform.lock.hcl) is shared.
If your stack uses 1.6+ features such as mock_provider, do a test run.
Parity is good, but edge cases exist.
State Encryption
OpenTofu 1.7+ can encrypt state on the client side before writing it to the backend:
terraform { encryption { key_provider "aws_kms" "key" {kms_key_id = "arn:aws:kms:..."
region = "us-east-1"
key_spec = "AES_256"
}
method "aes_gcm" "method" {keys = key_provider.aws_kms.key
}
state {method = method.aes_gcm.method
}
plan {method = method.aes_gcm.method
}
}
}
This is a genuine advantage: even if the backend (S3) is compromised, the state stored in it is encrypted. Terraform relies on the backend's SSE, which is sometimes insufficient. See tf-secrets-in-state.
exclude in for_each (OpenTofu 1.8)
variable "envs" {type = set(string)
default = ["dev", "stage", "prod", "test"]
}
resource "aws_s3_bucket" "logs" {for_each = var.envs
bucket = "logs-${each.key}" exclude {condition = each.key == "test"
}
}
This removes an element from for_each without reindexing. Terraform has no
equivalent; you need setsubtract(var.envs, ["test"]).
Matrix CI with Both Tools
The most practical response to the uncertainty is to run both:
jobs:
plan-terraform:
runs-on: ubuntu-latest
steps:
- uses: hashicorp/setup-terraform@v3
with: { terraform_version: 1.9.8 }- run: terraform init
- run: terraform plan
plan-opentofu:
runs-on: ubuntu-latest
steps:
- uses: opentofu/setup-opentofu@v1
with: { tofu_version: 1.8.0 }- run: tofu init
- run: tofu plan
If anything diverges, both jobs will show it. For the production apply you pick one tool: whichever owns the state.
This is a safety net. If HashiCorp changes the license again, you have a ready switch.
Pitfalls
-
registry.terraform.iovsregistry.opentofu.org. The providers are the same, but the registries are different. OpenTofu defaults to its own;~/.terraformrcor an environment variable overrides this. For air-gapped environments, mirror both. -
Terraform Cloud features. Sentinel policies, workspaces, dynamic credentials, and other Terraform Cloud features do not work in OpenTofu. OpenTofu can only read state from Terraform Cloud.
-
Version numbering divergence. Major version numbers do not correspond: OpenTofu 1.7 is not Terraform 1.7. Compare feature by feature, not by version number.
-
hcp-terraformblock in configuration. OpenTofu understands the syntax but does not connect. Leave it in place during the migration period; it causes no harm. -
Tooling divergence. Atlantis, Spacelift, Env0, and similar tools vary in their
tofusupport. Check before migrating. -
Provider signatures and checksums. OpenTofu uses its own public keys. If
terraform_required_providersspecifies an explicit hash, it may not match. Fix: remove the lockfile and reinitialize. -
The community is already split. An issue filed in one repository may not be reflected in the other. When reporting a bug, file in both.
-
State encryption is opt-in. Once enabled, every team member needs KMS access. Without KMS decrypt, no one can work with the state. Recovery after key loss is significantly harder.