Skip to main content

How to Migrate from Terraform to OpenTofu: Step-by-Step Guide

Key Takeaway

Complete guide to migrating from Terraform to OpenTofu. Install OpenTofu, migrate state files, update CI/CD pipelines, handle provider registries, and avoid common migration pitfalls.

Table of Contents

OpenTofu is a drop-in replacement for Terraform with an open-source MPL 2.0 license. Migration is straightforward for most projects — the CLI, HCL syntax, and state format are compatible. This guide covers the complete migration process including CI/CD updates and potential gotchas.

Prerequisites

  • Existing Terraform project (any version 1.x)
  • Access to your state backend (S3, GCS, Azure, etc.)
  • Ability to update CI/CD pipelines

Step 1: Install OpenTofu

macOS

brew install opentofu
tofu version

Linux (Ubuntu/Debian)

curl -fsSL https://get.opentofu.org/install-opentofu.sh -o install-opentofu.sh
chmod +x install-opentofu.sh
./install-opentofu.sh --install-method deb
tofu version

Linux (RHEL/Fedora)

curl -fsSL https://get.opentofu.org/install-opentofu.sh -o install-opentofu.sh
chmod +x install-opentofu.sh
./install-opentofu.sh --install-method rpm
tofu version

Docker

docker pull ghcr.io/opentofu/opentofu:1.9
docker run --rm -v $(pwd):/workspace -w /workspace ghcr.io/opentofu/opentofu:1.9 init

Step 2: Test in Your Project

cd your-terraform-project/

# Remove Terraform's cached providers (OpenTofu will re-download)
rm -rf .terraform
rm .terraform.lock.hcl

# Initialize with OpenTofu
tofu init

# Plan — should show NO changes
tofu plan

If tofu plan shows no changes, your migration is 90% done. The state file, HCL syntax, and providers all work.

Step 3: Verify State Compatibility

# Check state is readable
tofu state list

# Verify a specific resource
tofu state show aws_instance.web

# Compare with what Terraform showed
# (If you still have terraform installed)
terraform state list  # Should match exactly

Step 4: Update CI/CD

GitLab CI

# Before
stages:
  - plan
  - apply

plan:
  image: hashicorp/terraform:1.10
  script:
    - terraform init
    - terraform plan -out=tfplan
  artifacts:
    paths: [tfplan]

apply:
  image: hashicorp/terraform:1.10
  script:
    - terraform init
    - terraform apply -auto-approve tfplan

# After
plan:
  image: ghcr.io/opentofu/opentofu:1.9
  script:
    - tofu init
    - tofu plan -out=tfplan
  artifacts:
    paths: [tfplan]

apply:
  image: ghcr.io/opentofu/opentofu:1.9
  script:
    - tofu init
    - tofu apply -auto-approve tfplan

GitHub Actions

# Before
- uses: hashicorp/setup-terraform@v3
  with:
    terraform_version: "1.10.0"

# After
- uses: opentofu/setup-opentofu@v1
  with:
    tofu_version: "1.9.0"

- run: |
    tofu init
    tofu plan
    tofu apply -auto-approve    

Atlantis

# repos.yaml
repos:
  - id: "/.*/",
    workflow: opentofu

workflows:
  opentofu:
    plan:
      steps:
        - env:
            name: ATLANTIS_TERRAFORM_VERSION
            value: "1.9.0"
        - init
        - plan

Step 5: Update Wrapper Scripts

If you have shell scripts or Makefiles:

# Before
TF = terraform

# After — support both with a fallback
TF = $(shell command -v tofu 2>/dev/null || echo terraform)

plan:
	$(TF) init
	$(TF) plan

apply:
	$(TF) init
	$(TF) apply -auto-approve

Or use an alias for gradual migration:

# ~/.bashrc
alias terraform='tofu'

Step 6: Update required_version (Optional)

terraform {
  # Before — Terraform only
  required_version = ">= 1.7.0"

  # After — allow both Terraform and OpenTofu
  required_version = ">= 1.7.0"
  # OpenTofu respects the same constraint
}

Step 7: Enable State Encryption (Optional)

One advantage of OpenTofu — client-side state encryption:

terraform {
  encryption {
    key_provider "aws_kms" "state_key" {
      kms_key_id = "alias/terraform-state"
      region     = "us-east-1"
    }

    method "aes_gcm" "default" {
      keys = key_provider.aws_kms.state_key
    }

    state {
      method   = method.aes_gcm.default
      enforced = true
    }

    plan {
      method   = method.aes_gcm.default
      enforced = true
    }
  }
}

After enabling encryption, the state file is encrypted before it reaches your backend. Even if someone accesses your S3 bucket, they can’t read the state without the KMS key.

Common Migration Issues

Provider Registry

OpenTofu uses registry.opentofu.org by default but falls back to registry.terraform.io. Most providers work without changes:

# Works in both — source is the same
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

Lock File Differences

OpenTofu generates .terraform.lock.hcl in the same format. However, the hashes may differ between Terraform and OpenTofu for the same provider version. Re-run:

tofu providers lock \
  -platform=linux_amd64 \
  -platform=darwin_arm64

HCP Terraform (Terraform Cloud) Backend

This is the main migration blocker. If you use HCP Terraform as your backend:

# This does NOT work with OpenTofu
terraform {
  cloud {
    organization = "my-org"
    workspaces { name = "production" }
  }
}

You need to migrate to a different backend first:

# Switch to S3 backend
terraform {
  backend "s3" {
    bucket         = "my-tf-state"
    key            = "production/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}
# Migrate state from HCP to S3
terraform init -migrate-state
# Then switch to OpenTofu
tofu init

Sentinel → OPA

If you use Sentinel policies (HCP Terraform only), you’ll need to rewrite them as OPA/Rego policies:

# policy/no-public-s3.rego
package terraform.analysis

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_s3_bucket_public_access_block"
  resource.change.after.block_public_acls != true

  msg := sprintf("S3 bucket %s must block public ACLs", [resource.address])
}

Migration Checklist

  • Install OpenTofu on local machines
  • Run tofu init + tofu plan — verify no changes
  • Update CI/CD pipelines (image + command)
  • Update wrapper scripts/Makefiles
  • Regenerate .terraform.lock.hcl
  • Migrate off HCP Terraform backend (if applicable)
  • Rewrite Sentinel → OPA policies (if applicable)
  • Enable state encryption (optional)
  • Test apply in non-production first
  • Update team documentation

Hands-On Courses

Conclusion

Migrating from Terraform to OpenTofu is a 30-minute task for most projects: install OpenTofu, run tofu init, verify tofu plan shows no changes, and update your CI/CD pipeline. The main blocker is HCP Terraform — if you use it as a backend, you’ll need to migrate state to S3/GCS first. For self-hosted backends, the migration is seamless.

🚀

Level Up Your Terraform Skills

Hands-on courses, books, and resources from Luca Berton

Luca Berton
Written by

Luca Berton

DevOps Engineer, AWS Partner, Terraform expert, and author. Creator of Ansible Pilot, Terraform Pilot, and CopyPasteLearn.