ADR-025: Adopt OpenTofu over Terraform (BUSL)
Status: Accepted Date: 2026-10-06 Decision Makers: Human + AI Prompted By: HashiCorp relicensed Terraform under the Business Source License (BUSL) from v1.7 onward. The platform’s open-source licensing stance (already reflected in the kevinryan.io /method marketing, which advertises OpenTofu as the platform’s IaC choice) requires an MPL-2.0 toolchain. KRA-24.
Context
Section titled “Context”ADR-008 selected Terraform (then MPL-2.0) as the IaC framework for infra/. Since then, HashiCorp switched Terraform to the BUSL, which is not open source (OSI-incompatible, source-available with production-use restrictions for competing tooling). The infrastructure code, state, providers, and workflows all predate that relicensing.
The platform is otherwise fully open source: tflint (MPL-2.0), Kubernetes, Flux, Astro, Next.js, and the K3s stack. Terraform became the single non-open-licensed dependency in the toolchain.
Decision Drivers
Section titled “Decision Drivers”- The platform is a public repo that markets open, AI-native engineering practices; a BUSL-licensed core tool contradicts that stance.
- OpenTofu (Linux Foundation, MPL-2.0) is a drop-in fork of Terraform 1.5.7+: same CLI surface, same HCL language, same provider ecosystem, same
TF_VAR_*env convention, same azurerm state backend. - Migration risk must be near-zero: live production infrastructure on Azure, gated applies.
Options Considered
Section titled “Options Considered”Option A: Stay on Terraform (BUSL)
Section titled “Option A: Stay on Terraform (BUSL)”No migration work, but keeps a non-open-source license at the center of the IaC toolchain and accepts HashiCorp’s licensing direction.
Option B: OpenTofu, full swap in one pass
Section titled “Option B: OpenTofu, full swap in one pass”One change swaps the CLI locally, the CI workflow, the docs, and the skills. No state migration needed (same state file, same backend, same lock-file format). tflint is unaffected (MPL-2.0, CLI-agnostic).
Option C: OpenTofu, staged rollout
Section titled “Option C: OpenTofu, staged rollout”Local-first trial, then CI in a second pass. Two rounds of change and a dual-tool drift window, for no additional safety over a verified one-pass swap (the parity plan proves equivalence before anything merges).
Decision
Section titled “Decision”Adopt OpenTofu as the sole IaC CLI, migrating in one pass (Option B):
- Local CLI:
tofu(v1.13.1) replacesterraform. - CI:
.github/workflows/terraform.ymlbecomesopentofu.yml, using SHA-pinnedopentofu/[email protected]withtofu_version: 1.13.1pinned to match the local CLI (eliminating the prior CI/local version-lock workaround for decoding plan artifacts). - No state or backend migration: the azurerm remote state and
.terraform.lock.hclcarry over unchanged. - tflint remains the linter (MPL-2.0, works identically against OpenTofu configs).
Verification gate before merge: a local tofu plan must produce the same plan as terraform plan — verified: both show 0 to add, 1 to change (pre-existing, unrelated), 0 to destroy.
ADR-008’s core decision (single IaC framework, declarative Azure + Cloudflare management) stands unchanged; only the CLI choice is superseded by this ADR.
Consequences
Section titled “Consequences”Positive
Section titled “Positive”- The entire IaC toolchain is MPL-2.0 open source (OpenTofu + tflint).
- CI and local CLI versions are pinned to the same tofu version, so CI plan artifacts decode locally without downloading a matching binary.
- No state migration, no provider changes beyond constraint re-resolution, no downtime.
Negative
Section titled “Negative”.terraform.lock.hclre-keys providers fromregistry.terraform.io/*toregistry.opentofu.org/*and re-resolves versions within existing constraints on the firsttofu init(accepted; see Agent Decisions).- Terraform and OpenTofu cannot alternate on the same working directory without
init -reconfigure(each records its own backend hash) — irrelevant now thattofuis the only CLI. docs/terraform.mdmoves todocs/opentofu.md, so the old/terraform/docs URL 404s (static docs site, no redirect layer; acceptable).
- OpenTofu registry mirrors could lag registry.terraform.io for brand-new provider releases — mitigation: constraint re-resolution already landed the same latest versions; CI pins via lock file.
- Fork divergence in future OpenTofu-specific features could break Terraform compatibility — irrelevant; Terraform is no longer used.
Agent Decisions
Section titled “Agent Decisions”| Decision | Rationale | Acceptable |
|---|---|---|
| Accept the lock-file provider re-resolution (azurerm 4.62→4.81.0, azuread 3.8→3.10.0, cloudflare 4.52.5→4.52.9, random 3.8.1→3.9.1) rather than pinning back to the terraform-era versions | All within the declared ~> 4.0/~> 3.0 constraints; the parity plan ran against the new versions and showed zero new drift | Yes |
Pin tofu_version: 1.13.1 in CI to match local | Removes the CI/local version-lock pitfall (CI terraform 1.16.4 vs local 1.15.8 previously required downloading a matching binary to decode plan artifacts) | Yes |
| Leave kevinryan.io marketing copy mentioning Terraform (historical client work, the 2023 BUSL story) untouched | Historical facts and industry terms, not platform toolchain references; MethodStack already advertises OpenTofu | Review |
Rename plan-terraform-safely skill to plan-opentofu-safely and update its pitfalls (incl. the stale “CI passes no TF_VAR_*” claim) | Docs-first, same-commit rule; the stale claim predated the TF_VAR_* secret wiring in CI | Yes |
References
Section titled “References”- ADR-008: Infrastructure-as-Code with Terraform — original IaC decision (tooling choice superseded by this ADR)
- KRA-24 — Migrate Terraform to OpenTofu — work item with verification evidence
- OpenTofu — MPL-2.0, Linux Foundation
- opentofu/setup-opentofu — GitHub Action (pinned v2.0.2)