How-to
OpenTofu Migration Checklist
Move from Terraform to OpenTofu by pinning the binary, rehearsing plans in a clone of state, then cutting over CI — not by rewriting modules. A checklist for backends, providers, policy, and rollback.
OpenTofu migration · reference
Migrating to OpenTofu is a toolchain cutover. Read Terraform vs OpenTofu first. If you do not have a license or runner reason, stop here.
The checklist below assumes you already run remote state and CI plans. If you apply from laptops, fix that before you change the binary.
Before you touch production
- Written reason (legal, embed/redistribute, or a concrete CLI feature).
- Current Terraform version recorded (
terraform version) for every workspace. - Target OpenTofu version recorded. Do not float
latest. -
required_providersandrequired_versionconstraints reviewed. - List of wrappers:
make apply, custom images, pre-commit, Atlantis-style runners, policy CLIs that shell out toterraform.
Rehearsal (do this on a copy)
- Copy state to a scratch backend, or clone the workspace if your platform supports it.
- Install OpenTofu next to Terraform — different binary name, same working directory.
- Run init with the same backend config and provider pins.
- Run a plan. Require either no changes or a diff you can explain in one paragraph.
- If the diff is “noise” (provider bug, attribute reordering), file it. Do not invent a rewrite.
tofu version
tofu init -input=false
tofu plan -out=rehearsal.tfplan
tofu show -no-color rehearsal.tfplan
If rehearsal wants to recreate stateful resources, stop. That is a key or lifecycle problem, not an OpenTofu branding issue. See for_each vs count.
CI and policy
- Images and GitHub Actions call
tofu(or a pinned wrapper) explicitly. - OIDC roles still trust the same pipeline (OIDC for Terraform still applies).
- Policy engines read OpenTofu plan JSON. Confirm the schema path you parse.
-
terraform testor equivalent module tests run under the new binary (terraform test). - Docs and runbooks say which CLI production uses.
Cutover
- State lock works under OpenTofu against your backend (S3, Azure, GCS, etc.).
- One workspace, one CLI. Remove the old job so two applies cannot race.
- First production apply is a no-op or a trivial, reversible change.
- Previous Terraform version remains installable for a rollback window (several successful applies, not 45 minutes).
After
- Drift scan still runs — detection does not care what the binary is called (how to detect drift).
- Approvals still attach to a saved plan (who approves).
- New workspaces get the OpenTofu image by default so you do not fork the fleet.
You do not have to rewrite modules to change the CLI. If you generate HCL at all, keep the same rehearsal on Terraform / OpenTofu generation.
