TerraformPilot

DevOps

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

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

LLuca Berton2 min read

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.

#Terraform#OpenTofu#DevOps#IaC#Migration

Share this article