Software Engineer · Java · Spring Boot · Microservices
OpenTofu in Production: Migrating from HashiCorp Terraform to the Open-Source Fork Without Downtime
When HashiCorp announced the Business Source License change in August 2023, thousands of engineering teams running Terraform in production had an uncomfortable question to answer: is our current usage commercially compliant, and what do we do about it? The OpenTofu fork — now a Linux Foundation project with GA releases — provides a clean migration path. This guide walks through the full journey from audit to validated cutover, drawing on real-world migration experience across environments managing hundreds of Terraform modules and multiple cloud providers.
Table of Contents
- Introduction: The Licensing Wake-Up Call
- Real-World Problem: BSL Compliance and the Fork Decision
- Deep Dive: OpenTofu's History, CLI Differences, and New Features
- Solution Approach: Five-Phase Migration Plan
- Architecture and Code Examples
- Failure Scenarios and Trade-offs
- When NOT to Migrate to OpenTofu
- Optimization Techniques
- Key Takeaways
- Conclusion
1. Introduction: The Licensing Wake-Up Call
Picture a fintech platform team managing over 200 Terraform modules across three cloud providers. Their infrastructure-as-code setup has matured over four years: modular directory structures, remote state in S3 with DynamoDB locking, Atlantis for pull-request-driven applies, and a carefully pinned provider version matrix. On a Thursday morning in August 2023, an engineer on the platform team reads the HashiCorp announcement — effective immediately, Terraform is moving from the Mozilla Public License 2.0 to the Business Source License 1.1. The team's legal counsel flags a concern by Friday: the company offers a platform that competes, even tangentially, with HashiCorp's own managed offerings. Under BSL, that could constitute restricted use.
This is not a hypothetical. Dozens of fintech, SaaS, and platform engineering teams went through exactly this evaluation in late 2023. The question wasn't whether Terraform worked — it clearly did, and the team had invested heavily in it. The question was whether continuing to use it constituted a compliance risk that legal wasn't comfortable with. The OpenTofu fork, announced within weeks of the BSL change and accepted into the Linux Foundation by September 2023, offered an answer: a community-governed, MPL-2.0-licensed fork that preserved full CLI and state file compatibility while committing to open governance indefinitely.
2. Real-World Problem: BSL Compliance and the Fork Decision
The Business Source License 1.1 restricts use in a "production" capacity by any entity whose product competes with the licensor's commercial offerings. HashiCorp's Additional Use Grant defines "competing use" broadly. For pure internal infrastructure management with no product commercialization, the risk is low. But for companies building developer platforms, cloud cost management tooling, or anything resembling managed infrastructure services, legal teams often concluded the risk warranted a migration.
Beyond licensing, the fork decision surfaced two technical questions that the fintech team needed to answer before committing. First: state file compatibility. Terraform's .tfstate format is JSON-serialized, and OpenTofu inherited the exact same schema. A state file created by terraform apply can be read and modified by tofu apply without conversion, as long as you stay within the compatible version range (Terraform <= 1.6.x when migrating to OpenTofu 1.6.x). Second: provider registry. Terraform uses registry.terraform.io as the default registry hostname. OpenTofu ships its own registry at registry.opentofu.org, which mirrors all public Terraform providers — but the resolution logic differs, and provider source addresses in your .tf files may need updating.
Teams running Terraform Cloud or HCP Terraform face a more complex calculus. Terraform Cloud provides Sentinel policy enforcement, drift detection, cost estimation, and a run-queue UI that many enterprises depend on. OpenTofu has no equivalent managed SaaS product — it's a CLI tool designed to work with any compatible backend. Teams deeply embedded in TFC workflows need to evaluate open-source alternatives like Atlantis, Spacelift, or Env0 as part of their migration plan, which significantly increases scope.
3. Deep Dive: OpenTofu's History, CLI Differences, and New Features
The OpenTofu fork timeline is worth understanding because it explains the feature trajectory. HashiCorp announced the BSL change on August 10, 2023. The OpenTofu initiative launched August 25, 2023, with a pledge signed by over 100 companies including Spacelift, Env0, Gruntwork, Harness, and Cloudflare. The Linux Foundation accepted OpenTofu as a project on September 20, 2023. OpenTofu 1.6.0 GA shipped January 10, 2024. By early 2025, OpenTofu had diverged meaningfully from Terraform 1.6 with two landmark features: provider-defined functions (1.7) and client-side state encryption (1.7). These features were explicitly declined by HashiCorp for upstream Terraform in the period leading up to the BSL change, making them OpenTofu-only innovations.
The CLI binary is named tofu instead of terraform. All subcommands are identical: tofu init, tofu plan, tofu apply, tofu destroy, tofu state. Environment variables use the TF_ prefix for backward compatibility — so TF_VAR_region=us-east-1 works with tofu exactly as it does with terraform. The .terraform.lock.hcl dependency lock file format is identical; the lock file generated by tofu providers lock is interchangeable with Terraform's. The only behavioral difference in the base flow is that tofu init resolves providers from registry.opentofu.org by default rather than registry.terraform.io.
Client-side state encryption, introduced in OpenTofu 1.7, is a feature that encrypts the state file before it reaches the remote backend, using a key you control. This is significant for teams storing state in shared S3 buckets, where a misconfigured bucket ACL or a compromised AWS credential could expose plain-text infrastructure state — which often contains database passwords, API keys, and private IP ranges. The encryption is transparent to normal operations: tofu plan decrypts automatically using the configured key provider (AWS KMS, GCP KMS, PBKDF2 passphrase, or OpenBao). Terraform has no equivalent, requiring third-party tooling or manual state sanitization.
4. Solution Approach: Five-Phase Migration Plan
Successful migrations we've observed follow a five-phase structure. Each phase is independently reversible, which is critical when you're managing live infrastructure that cannot afford downtime.
Phase A — Audit. Run terraform version across all pipelines and record exact versions. Enumerate every module's provider source addresses. Flag any module using source = "hashicorp/..." shorthand — these implicitly resolve to registry.terraform.io/hashicorp/... and will need explicit source blocks updated to use OpenTofu's registry or a mirror. Check for Terraform Cloud remote backend blocks (backend "remote" or cloud {}) — these target HCP Terraform's API directly and will need to be replaced with an alternative backend (S3, GCS, Consul, or Spacelift remote state).
Phase B — CI/CD switch. Replace the hashicorp/setup-terraform GitHub Actions action with opentofu/setup-opentofu. Update any pipeline scripts that call terraform to call tofu. Run pipelines in plan-only mode (no apply) to confirm the new CLI resolves providers and reads state without errors. This is a zero-risk phase — no state is written.
Phase C — State backend migration (if needed). Teams leaving Terraform Cloud must migrate remote state. Export state with terraform state pull > backup.tfstate, configure the new backend in your main.tf, and run tofu init -migrate-state. Test state reads with tofu state list before running any plan.
Phase D — Provider pin updates. Update required_providers blocks to explicitly set source addresses. Run tofu providers lock -platform=linux_amd64 -platform=darwin_arm64 to regenerate the lock file with OpenTofu registry checksums. This is where hash mismatches are most likely to surface (see Failure Scenarios).
Phase E — Validation runs. Execute tofu plan against production state and verify zero-diff for all existing resources. Any unexpected changes in the plan output signal a provider version mismatch or a behavioral difference between Terraform and OpenTofu provider plugins. Rollback is straightforward: re-pin to the Terraform CLI version in your pipeline, and since state files are binary-compatible, no state manipulation is needed.
5. Architecture and Code Examples
Start by installing the tofu CLI alongside your existing Terraform installation. Both can coexist since they use different binary names:
# Install OpenTofu via official install script (Linux/macOS)
curl --proto '=https' --tlsv1.2 -fsSL https://get.opentofu.org/install-opentofu.sh \
| sh -s -- --install-method standalone
# Verify both CLIs coexist
terraform version # Terraform v1.6.6
tofu version # OpenTofu v1.7.3
# Test against existing state (read-only, no changes written)
cd your-module/
tofu init
tofu plan -out=tfplan
tofu show tfplan | head -40
The GitHub Actions workflow change is minimal. Replace the setup action and rename the binary:
# .github/workflows/tofu-plan.yml
name: OpenTofu Plan
on:
pull_request:
branches: [main]
permissions:
id-token: write
contents: read
pull-requests: write
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# BEFORE: uses: hashicorp/setup-terraform@v3
- name: Setup OpenTofu
uses: opentofu/setup-opentofu@v1
with:
tofu_version: "1.7.3"
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1
- name: OpenTofu Init
run: tofu init -backend-config="bucket=${{ secrets.STATE_BUCKET }}"
- name: OpenTofu Plan
id: plan
run: tofu plan -no-color -out=tfplan 2>&1 | tee plan_output.txt
- name: Post Plan to PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const output = fs.readFileSync('plan_output.txt', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `<details><summary>OpenTofu Plan</summary>\n\n\`\`\`\n${output}\n\`\`\`\n</details>`
});
Managing parallel provisioning jobs in CI pipelines shares design principles with concurrent task lifecycle management — a concept we explored in detail in our post on Java Structured Concurrency, where bounded fan-out with cancellation guarantees maps directly to pipeline stage orchestration.
Update your provider source block to be explicit about the registry. While OpenTofu's registry mirrors all providers from Terraform's registry, being explicit avoids ambiguity:
# providers.tf — explicit source addresses for OpenTofu registry
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "registry.opentofu.org/hashicorp/aws"
version = "~> 5.40"
}
kubernetes = {
source = "registry.opentofu.org/hashicorp/kubernetes"
version = "~> 2.28"
}
helm = {
source = "registry.opentofu.org/hashicorp/helm"
version = "~> 2.13"
}
}
backend "s3" {
bucket = "my-tofu-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "tofu-state-lock"
encrypt = true
}
}
OpenTofu 1.7's client-side state encryption feature is configured via a new encryption block in your root module. Here is an example using AWS KMS:
# encryption.tf — client-side state encryption (OpenTofu 1.7+, no Terraform equivalent)
terraform {
encryption {
key_provider "aws_kms" "primary" {
kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/mrk-abc123"
region = "us-east-1"
key_spec = "AES_256"
}
method "aes_gcm" "state_enc" {
keys = key_provider.aws_kms.primary
}
# Encrypt both state and plan files
statefile {
method = method.aes_gcm.state_enc
}
planfile {
method = method.aes_gcm.state_enc
}
}
}
6. Failure Scenarios and Trade-offs
Provider plugin hash mismatch. The most common failure during migration. When you run tofu providers lock, OpenTofu fetches provider checksums from registry.opentofu.org. If your existing .terraform.lock.hcl was generated by Terraform against registry.terraform.io, the checksums will differ even for identical provider binaries — the registries use independent signing keys. The fix is to delete the lock file and regenerate it with tofu providers lock -platform=linux_amd64 -platform=darwin_arm64 -platform=windows_amd64 before committing. Always commit the regenerated lock file to your repository so that CI and local developer environments use consistent checksums.
Sentinel policy incompatibility. Sentinel is HashiCorp's policy-as-code framework, integrated only into Terraform Cloud and Terraform Enterprise. OpenTofu has no Sentinel support. Teams enforcing Sentinel policies for compliance (e.g., "all S3 buckets must have versioning enabled") must migrate to an alternative enforcement mechanism: Open Policy Agent (OPA) with Conftest is the most common choice, as it evaluates the tofu plan -json output against Rego policies in your CI pipeline.
Terragrunt version pinning issues. Terragrunt calls the underlying terraform binary by default. You must set terraform_binary = "tofu" in your root terragrunt.hcl or set the TERRAGRUNT_TFPATH=tofu environment variable. Terragrunt versions earlier than 0.54.0 may have inconsistencies in how they detect and invoke OpenTofu, so pinning to a recent Terragrunt release is advised.
Remote state locking differences. OpenTofu respects all backend locking mechanisms that Terraform supports (DynamoDB for S3, Consul sessions, GCS object metadata). However, if you switch backends during the migration — for example, moving from TFC remote state to S3 — there is a window between the old backend releasing its lock and the new backend acquiring one. Always migrate state during a maintenance window or when no concurrent applies are running.
7. When NOT to Migrate to OpenTofu
OpenTofu is not the right choice for every team, and a migration has real costs. If your organization's legal team has reviewed the BSL and concluded that your use case falls within HashiCorp's Additional Use Grant — pure internal infrastructure management with no competitive overlap — the migration provides no compliance benefit and introduces unnecessary risk.
Teams deeply embedded in HCP Terraform or Terraform Cloud should be cautious. The run queue, workspace UI, VCS-driven runs, team-based access control, Sentinel policies, cost estimation, and drift detection are features with no direct OpenTofu equivalent. You would be replacing a managed product with a set of open-source tools (Atlantis + OPA + Infracost + a self-managed runner). That is a significant operational investment that may not be justified if your BSL risk is low.
Organizations running Terraform Enterprise on-premise with audit logging requirements should defer migration until mature commercial alternatives (Spacelift, Env0, Scalr) have completed their compliance certification processes for regulated industries. The CLI migration itself is straightforward; replacing TFE's audit trail and LDAP integration is not.
Finally, if you are on Terraform 1.7 or later, migrating to OpenTofu 1.6.x will represent a downgrade in HCL feature support. You would need to migrate to OpenTofu 1.7+ to maintain feature parity, which introduces the additional new features (encryption, provider-defined functions) that need testing in your environment.
8. Optimization Techniques
Parallelism tuning. OpenTofu's -parallelism flag controls how many resource operations are dispatched concurrently during a plan or apply. The default is 10. For large plans touching hundreds of resources, increasing to 20–30 can cut apply times significantly on high-bandwidth connections to cloud APIs. However, many cloud providers enforce per-second API rate limits — AWS IAM, for example, has strict throttling on CreateRole and PutRolePolicy calls. A -parallelism=50 setting against an IAM-heavy plan will generate API throttling errors. The right value is empirically determined per workspace and provider combination. The -parallelism flag in OpenTofu controls how many resource operations run concurrently — conceptually similar to the fork concurrency model in Java's StructuredTaskScope, where you want bounded parallelism with guaranteed lifecycle cleanup.
Provider plugin caching. By default, every tofu init run in CI downloads provider plugins from the registry. For pipelines running dozens of times a day, this wastes bandwidth and adds latency. Set TF_PLUGIN_CACHE_DIR=/tmp/tofu-plugin-cache in your CI environment and mount that directory as a GitHub Actions cache keyed on the lock file hash. Provider downloads that previously took 30–45 seconds drop to under 2 seconds on cache hit.
# GitHub Actions provider cache
- name: Cache OpenTofu providers
uses: actions/cache@v4
with:
path: ~/.tofu.d/plugin-cache
key: tofu-providers-${{ hashFiles('**/.terraform.lock.hcl') }}
restore-keys: tofu-providers-
- name: Configure plugin cache dir
run: |
mkdir -p ~/.tofu.d/plugin-cache
cat >> ~/.tofurc << 'EOF'
plugin_cache_dir = "$HOME/.tofu.d/plugin-cache"
EOF
- name: OpenTofu Init
run: tofu init
env:
TF_PLUGIN_CACHE_DIR: ~/.tofu.d/plugin-cache
Module registry mirror. For air-gapped environments or organizations wanting to control provider versions centrally, set up a local registry mirror using a simple nginx static file server or a dedicated tool like terraform-provider-mirror. Configure OpenTofu to use it with a provider_installation block in your .tofurc global config. This eliminates all external registry calls in CI and standardizes provider versions across all teams.
OpenTofu's built-in testing framework. OpenTofu 1.7 ships a native tofu test command that executes .tftest.hcl test files. These tests spin up real infrastructure, run assertions against output values and resource attributes, then tear everything down. Unlike terratest (Go-based external testing), tofu test integrates directly with the HCL language, allowing you to write assertions in the same configuration language as your modules. Adding unit tests to critical shared modules — VPC, EKS cluster, IAM role factory — gives you regression protection during version upgrades.
# tests/vpc.tftest.hcl — native OpenTofu test
variables {
vpc_cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
}
run "creates_vpc_with_correct_cidr" {
command = plan
assert {
condition = aws_vpc.main.cidr_block == var.vpc_cidr
error_message = "VPC CIDR block does not match input variable"
}
}
run "private_subnets_not_public" {
command = apply
assert {
condition = alltrue([for s in aws_subnet.private : s.map_public_ip_on_launch == false])
error_message = "Private subnets must not auto-assign public IPs"
}
}
9. Key Takeaways
- OpenTofu is a drop-in CLI replacement for Terraform <= 1.6.x; state files are binary-compatible and the
tofubinary accepts all the same subcommands and environment variables. - Provider plugin hash mismatches are the most common migration failure; delete and regenerate
.terraform.lock.hclusingtofu providers lockfor all target platforms. - Teams leaving Terraform Cloud must plan backend and policy enforcement migrations separately from the CLI switch; Sentinel has no OpenTofu equivalent — OPA with Conftest is the standard replacement.
- OpenTofu 1.7's client-side state encryption is a genuinely new security capability unavailable in Terraform; teams with sensitive state files should evaluate it as part of migration planning.
- Provider plugin caching with
TF_PLUGIN_CACHE_DIRand the-parallelismflag are the two highest-leverage performance optimizations for CI pipeline throughput. - The native
tofu testframework enables HCL-native module testing without external Go frameworks, significantly lowering the barrier to infrastructure test coverage.
10. Conclusion
The OpenTofu migration is remarkably low-risk for teams not relying on Terraform Cloud's managed features. The fintech team we described at the start completed their migration of 200+ modules over six weeks: two weeks for the audit and CI switch, two weeks for provider lock regeneration and validation runs across all environments, and two weeks of parallel running with both CLIs available before the full cutover. Their production infrastructure experienced zero incidents attributable to the migration.
What they gained beyond license compliance was instructive: the client-side state encryption feature alone justified the migration cost for their security team, eliminating a long-standing concern about plain-text database credentials in S3 state files. The native testing framework gave them a path to proper module regression testing that they had previously deprioritized because terratest's Go boilerplate was too high a barrier for infrastructure engineers.
OpenTofu's Linux Foundation governance, growing contributor community, and the velocity of new feature development suggest it is not merely a compliance escape hatch but a genuinely improving platform. For teams evaluating IaC tooling choices in 2025 and beyond, it deserves serious consideration alongside Terraform, Pulumi, and CDK — not just as a fallback, but as a first choice for open-source, vendor-neutral infrastructure management.
Discussion
Have you migrated from Terraform to OpenTofu? Share your experience, gotchas, or questions below.
Related Posts
Infrastructure as Code with Terraform
Master Terraform modules, remote state, workspaces, and production IaC patterns at scale.
GitOps with ArgoCD
Implement GitOps continuous delivery for Kubernetes with ArgoCD sync strategies and rollback.
CI/CD with GitHub Actions
Build production-grade CI/CD pipelines with GitHub Actions, environments, and secrets management.
Last updated: March 2026 — Written by Md Sanwar Hossain