OpenTofu vs Terraform: Complete Migration Guide for Infrastructure as Code

OpenTofu vs Terraform: Making the Right Infrastructure Decision

OpenTofu Terraform migration has become one of the most discussed topics in the infrastructure-as-code community since HashiCorp changed Terraform’s license from MPL 2.0 to BSL 1.1 in August 2023. Consequently, organizations must decide whether to stay with Terraform, migrate to OpenTofu, or adopt a hybrid approach. This guide provides a thorough comparison and a step-by-step migration path for production infrastructure, along with the trade-offs that rarely make it into vendor marketing.

OpenTofu is a fork of Terraform maintained under the Linux Foundation, which keeps it genuinely open source under the MPL 2.0 license. Moreover, OpenTofu maintains backward compatibility with most Terraform configurations, state files, and providers. Therefore, migration is straightforward for the majority of teams. However, understanding where the two tools diverge — and planning the cutover carefully — is essential for a transition that does not surprise you mid-apply.

OpenTofu Terraform Migration: Feature Comparison

Both tools share the same core HCL syntax and a large portion of the provider ecosystem, but they have begun diverging in meaningful ways. OpenTofu has shipped several features that Terraform either lacks or gated behind its commercial tiers, including client-side state encryption, early variable evaluation in backend and module blocks, and provider-defined functions. Furthermore, OpenTofu’s governance model puts roadmap decisions in the hands of a technical steering committee rather than a single vendor, whereas Terraform’s direction is ultimately set by HashiCorp’s commercial priorities. In practice, this matters most for teams that want to influence the roadmap or avoid future license surprises.

It is worth being precise about compatibility. OpenTofu tracks Terraform’s feature set closely but is not a perfect superset: state files written by a newer Terraform version may carry a schema that an older OpenTofu release does not understand, and a handful of newer Terraform-only language features will not parse under OpenTofu. As a rule of thumb, a configuration that worked on Terraform 1.5.x migrates cleanly, because that is the last shared MPL-licensed lineage both tools descend from. Teams on Terraform 1.6 or later should test feature-by-feature rather than assuming parity.

# OpenTofu exclusive: Client-side state encryption
terraform {
  encryption {
    method "aes_gcm" "default" {
      keys = key_provider.aws_kms.main
    }
    state {
      method   = method.aes_gcm.default
      enforced = true
    }
    plan {
      method   = method.aes_gcm.default
      enforced = true
    }
  }

  backend "s3" {
    bucket         = "my-tofu-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "tofu-locks"
    encrypt        = true
  }
}

# Provider configuration remains identical
provider "aws" {
  region = "us-east-1"
  default_tags {
    tags = {
      ManagedBy   = "OpenTofu"
      Environment = "production"
    }
  }
}

The encryption block above is the single most cited reason teams switch. With S3 server-side encryption, your state is protected at rest in the bucket, but anyone who can read the object — or who intercepts it in a CI log or local cache — sees plaintext secrets. Client-side encryption closes that gap because the state is unreadable without the KMS key, even to an operator who has bucket access. Notably, this is a capability Terraform’s open-source distribution does not offer at all.

OpenTofu infrastructure as code migration
OpenTofu provides a truly open-source alternative to Terraform with enhanced security features

Step-by-Step Migration from Terraform to OpenTofu

The migration process is designed to be non-disruptive. Since OpenTofu reads Terraform state directly, you can migrate incrementally — one root module at a time. Additionally, both binaries can coexist in the same CI/CD pipeline during the transition, which lets you validate each step before committing. The critical discipline is simple: never run an apply with the new tool until a plan shows zero changes. A non-empty plan after a tool swap is a red flag that something — usually a provider version or a default attribute — has drifted.

# Step 1: Install OpenTofu
curl --proto '=https' --tlsv1.2 -fsSL \
  https://get.opentofu.org/install-opentofu.sh | sh

# Step 2: Verify compatibility with existing state
cd /path/to/terraform-project
tofu init    # Downloads providers, reads existing state
tofu plan    # MUST show "No changes" before you proceed

# Step 3: Update CI/CD pipeline (GitHub Actions example)
# Replace hashicorp/setup-terraform with opentofu/setup-opentofu

# Step 4: Lock file migration — regenerate hashes for all platforms
tofu providers lock \
  -platform=linux_amd64 \
  -platform=darwin_amd64 \
  -platform=darwin_arm64

# Step 5: Validate all modules
tofu validate
tofu fmt -check -recursive

Step 4 deserves extra attention because it is where silent failures hide. The .terraform.lock.hcl file pins provider hashes, and OpenTofu pulls providers from its own registry mirror by default. If your lock file contains only registry.terraform.io hashes, a CI runner on a different OS may refuse to install. Regenerating the lock for every platform your team and pipelines use prevents the dreaded “checksum mismatch” error that otherwise appears only on a colleague’s laptop.

State Management and Backend Compatibility

OpenTofu supports the same backends as Terraform, including S3, Azure Blob Storage, Google Cloud Storage, and Consul. The state file format is identical, so you can switch tools without a state migration step. As shown earlier, OpenTofu layers client-side encryption on top, giving you an extra defense for sensitive values such as database passwords and private keys that inevitably end up in state. The module layer is likewise unchanged — the same source addresses and version constraints resolve correctly.

# Module structure remains identical
module "vpc" {
  source  = "registry.opentofu.org/terraform-aws-modules/vpc/aws"
  version = "5.5.0"

  name = "production-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["us-east-1a", "us-east-1b", "us-east-1c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  enable_nat_gateway     = true
  single_nat_gateway     = false
  one_nat_gateway_per_az = true

  tags = {
    Terraform   = "false"
    OpenTofu    = "true"
    Environment = "production"
  }
}

# OpenTofu registry is compatible with Terraform registry
module "eks" {
  source  = "registry.opentofu.org/terraform-aws-modules/eks/aws"
  version = "20.8.0"

  cluster_name    = "prod-cluster"
  cluster_version = "1.29"

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  eks_managed_node_groups = {
    general = {
      instance_types = ["m6i.xlarge"]
      min_size       = 3
      max_size       = 10
      desired_size   = 5
    }
  }
}
Cloud infrastructure management
State management in OpenTofu is fully compatible with existing Terraform backends

Handling Provider and Registry Differences

One subtle edge case catches teams off guard: module and provider source addresses. OpenTofu defaults to registry.opentofu.org, which mirrors the public Terraform registry, so most community modules resolve identically. Nevertheless, if your configuration hard-codes registry.terraform.io or relies on a HashiCorp private registry, you must either keep that address explicit or point OpenTofu at an alternative. Private registries are the most common sticking point, because Terraform Cloud’s private registry is not directly reachable from OpenTofu without re-publishing modules to a self-hosted or OpenTofu-compatible registry.

For mixed estates, a pragmatic pattern is to introduce a network mirror. You configure a single filesystem or HTTP mirror that both tools consume, decoupling your pipelines from any single upstream registry. This is also a resilience win — your applies no longer break when a public registry has an outage. The OpenTofu docs describe the provider_installation block that drives this behavior, and it accepts the same syntax Terraform uses.

CI/CD Pipeline Updates for OpenTofu

Updating your pipeline is mostly mechanical because the CLI commands are nearly identical — you swap terraform for tofu. Furthermore, OpenTofu ships official GitHub Actions, GitLab CI templates, and a Jenkins-friendly binary, so the plumbing rarely needs a rewrite. The example below pins an explicit OpenTofu version, which you should always do; floating versions in CI is how a minor upgrade silently changes a plan and erodes trust in the pipeline.

# GitHub Actions workflow for OpenTofu
name: Infrastructure Deploy
on:
  push:
    branches: [main]
    paths: ['infra/**']

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4

      - uses: opentofu/setup-opentofu@v1
        with:
          tofu_version: 1.7.0

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/tofu-deploy
          aws-region: us-east-1

      - name: Init
        run: tofu init -backend-config=prod.hcl
        working-directory: infra/

      - name: Plan
        run: tofu plan -out=plan.tfplan
        working-directory: infra/

      - name: Apply
        if: github.ref == 'refs/heads/main'
        run: tofu apply plan.tfplan
        working-directory: infra/

A useful transitional tactic is to run both tools in parallel for a sprint: keep the Terraform job as a non-blocking check while OpenTofu becomes the source of truth. If the two plans ever disagree, the pipeline surfaces it before an operator does. Once the diffs stay empty for a week or two, you retire the Terraform job with confidence rather than on faith.

When NOT to Migrate

OpenTofu is excellent for most use cases, but several scenarios genuinely favor staying put. If your workflow leans on Terraform Cloud or Terraform Enterprise features — Sentinel policy-as-code, integrated cost estimation, the private module registry, or the run UI — the migration cost rises sharply, because OpenTofu does not reproduce that managed control plane. You would need to assemble equivalents such as Open Policy Agent for policy and a self-hosted backend for the run experience. Similarly, organizations under an existing HashiCorp Enterprise agreement should weigh contract obligations and support coverage before switching, since you trade vendor support for community support.

There are also conservative reasons to wait. If your infrastructure is stable, your team is small, and you have no compliance driver for client-side state encryption, the migration delivers little immediate value and carries non-zero risk. The honest trade-off is this: OpenTofu buys you license certainty and a few security features, but it asks you to own more of the tooling yourself. For teams that valued Terraform precisely because HashiCorp owned that complexity, the calculus may favor staying. See the OpenTofu documentation for the current compatibility matrix before you decide.

Key Takeaways

  • Migrate one root module at a time and never apply until tofu plan reports no changes
  • Regenerate .terraform.lock.hcl for every platform your team and CI use
  • Audit private-registry and hard-coded registry source addresses before cutover
  • Pin explicit tool versions in CI so an upgrade never silently alters a plan
  • Stay on Terraform if you depend on Cloud/Enterprise features like Sentinel or the private registry
DevOps decision making for infrastructure tools
Evaluate your specific requirements before deciding on the migration timeline

In conclusion, OpenTofu Terraform migration is a low-risk, high-reward move for organizations committed to open-source infrastructure management, provided you respect the edge cases around lock files, registries, and managed-platform features. The identical state format, the largely compatible provider ecosystem, and the near-drop-in CLI make the transition smooth in the common case. Start in non-production, validate your workflows until plans stay empty, and roll out to production only once the tooling has earned your team’s trust.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top