Post
PT EN

Migrating from Terraform to OpenTofu: A Safe, Reversible Process

Migrating from Terraform to OpenTofu: A Safe, Reversible Process

Introduction

Swapping the binary is the easy part of the migration. What usually stalls the decision is the state file.

The concern is reasonable. State holds the record of everything already provisioned, and a problem there doesn’t just turn a pipeline red, it turns into a tool proposing to recreate resources that are running in production.

The good news is there’s an official, tested procedure, with well-defined steps and a documented way back. It’s more careful than most tutorials suggest, and that care is exactly what makes the operation safe.

Let’s walk through this procedure from start to finish, including what to do when something doesn’t go as expected.

The Principle: Migrate by Matching Version

This is the point almost every informal tutorial gets wrong, worth starting here.

The official recommendation isn’t to install the latest OpenTofu version and run it against your code. It’s to migrate first to the OpenTofu version that matches your current Terraform version, and only then upgrade to the newest one.

If you’re on Terraform 1.9.x, the path is to migrate to OpenTofu 1.9.0 and, once the migration is complete and validated, follow the upgrade guide up to the current version.

The reason is simple. Each migration guide is written for a specific pair of versions, because the differences between them are known and documented. Skipping steps mixes two distinct sources of change into a single operation: swapping tools and jumping versions. If something breaks, it becomes hard to tell which one caused it.

There’s also a precision detail: the guides require a specific Terraform patch version, not just the minor line. You need to be on exactly that version before starting.

What Happens to the State

Before the procedure, it’s worth understanding what changes in the file that causes all the apprehension.

OpenTofu reads state generated by Terraform directly, with no conversion. The format is the same, and the field indicating the format version stays untouched.

What changes is a different field, the one recording which tool last wrote the state. It gets updated when OpenTofu writes to it, which only happens on the first apply. As long as you only run init and plan, nothing gets written.

That’s the basis of reversibility. The entire validation phase happens in read-only mode, and you decide whether to proceed or stop before any change gets recorded.

Step 1: Recovery Plan and Backup

The official procedure opens with a recommendation that isn’t bureaucracy: have an up-to-date, tested disaster recovery plan.

In practice, that’s two things.

If state is local, copy the file:

1
cp terraform.tfstate terraform.tfstate.backup-pre-migration

If state lives in a remote backend, follow that backend’s own backup procedure and, above all, run the restore procedure at least once. A backup never tested isn’t a backup, it’s an assumption.

Commit the code too, since the migration will require small changes to it.

Step 2: Clear Out Pending Changes

Apply everything pending with Terraform, before touching OpenTofu:

1
terraform apply

The goal is to reach a clean state, where the plan proposes no changes. That’s your baseline.

The reason is methodological. Migrating with pending changes, you won’t be able to tell what OpenTofu proposed due to behavior differences apart from what was already pending. Without a baseline, there’s no valid comparison.

Step 3: Align the Terraform Version

Check your current version and adjust to whatever the matching guide requires:

1
terraform version

If you’re below the indicated patch version, upgrade Terraform first. If you’re above every version covered by the existing guides, the recommended path is to wait for the right guide to be published, rather than improvising.

Step 4: Code Adjustments

Here are the changes the migration calls for. They vary by source version, and the most common are:

  • S3 backend. If you use the option to skip checksum verification, drop it. OpenTofu doesn’t need it.
  • Endpoints and SSO. If you set custom endpoints through that path or the corresponding environment variable, remove the configuration and confirm the behavior holds.
  • The removed block. Behavior differs from its Terraform equivalent. Review the docs and adjust, dropping the associated lifecycle block.
  • Native tests. If you use the testing feature with resource or data overrides inside a mocked provider, you’ll need to restructure those tests.

Check the guide for your specific version, since the full list depends on it. Treat this section as a reminder of the kind of adjustment to expect, not a definitive inventory.

Step 5: Initialize and Compare

Now OpenTofu enters the picture:

1
2
tofu init
tofu plan

The expected plan outcome is no changes at all. If unexpected changes show up, don’t proceed. The official guidance is explicit on this: if any step fails, go back to Terraform and investigate before continuing.

It’s worth recording the plan to a file for later comparison:

1
2
3
terraform plan -no-color > terraform-plan.txt
tofu plan -no-color > tofu-plan.txt
diff terraform-plan.txt tofu-plan.txt

Formatting differences are normal. Resources flagged for creation, change, or destruction are not.

Step 6: Apply, Then Upgrade

With a clean plan, apply:

1
tofu apply

This is the moment state starts recording OpenTofu as the writing tool.

Next, make a small, non-critical change, like adding a tag to a resource, and apply again. This confirms the tool can actually manage your infrastructure going forward, not just read what already exists.

Once validation is done, only then upgrade OpenTofu to the latest version, following the project’s own upgrade guide. That order matters.

How to Roll Back

Rolling back is a documented procedure, not improvisation. If something goes off script:

  1. Stop using OpenTofu immediately. Don’t try to fix it with more commands.
  2. Take another backup of the current state and code.
  3. Undo the code changes made in Step 4.
  4. Run terraform init, then terraform plan.
  5. Confirm no unexpected changes show up in the plan.
  6. Run terraform apply and verify it executes with no changes.
  7. Test with a small, non-critical change, to confirm the workflow is back to normal.

If the cause looks like a tool defect, the project asks that the issue be reported in the repository. That helps whoever comes next.

Common Errors

Two problems account for most reports.

Provider unavailable in the registry. The message indicates a failure querying available provider packages. It means a provider declared in your configuration doesn’t exist in OpenTofu’s registry. The guidance is to roll back to Terraform and review that dependency.

Failure loading the provider schema. This happens when state hasn’t been written by OpenTofu yet and the configuration declares the provider source with Terraform’s full registry name. The fix is to simplify the declaration, using just the organization and provider name, without the registry prefix.

Gradual Migration

One last piece of process advice: don’t migrate everything at once.

Start with the least critical repository, preferably a development environment with few resources. Walk through the entire procedure, including testing the rollback. Each following repository goes faster, since the code adjustments specific to your organization are already known.

That cadence turns a risky migration into a sequence of predictable operations.

Conclusion

It’s worth separating two questions that tend to get conflated in this discussion. One is whether the migration is technically viable. The other is whether it should be done.

The first one is answered. The procedure exists, is official, has defined steps and a documented way back. The entire validation phase happens in read-only mode, and state only gets written once you decide to proceed. The risk isn’t in the tool, it’s in skipping steps: migrating with pending changes, jumping versions, or treating backup as a formality.

The second question is yours, and has no single answer. It depends on your company’s business model, on the features you rely on today, and on how much weight licensing predictability carries in your medium-term planning.

What a well-defined procedure does is take the fear out of the equation. As long as the migration feels like a leap in the dark, the conversation stays stuck in impressions and preferences. Once it becomes a known, reversible sequence, the choice between the two tools stops being a bet and becomes a comparison. And comparisons get resolved with criteria.

This post is licensed under CC BY 4.0 by the author.