Hi everyone. I'm in the middle of migrating our IaC from Terraform to OpenTofu. The actual state import and plan/apply steps went okay using the `tofu` CLI.
But now our CI/CD pipeline is broken. It's a GitLab CI job that runs `terraform init`, `terraform plan`, and `terraform apply`. The runner fails because `terraform` isn't installed, only `tofu`.
What's the best way to handle this? Should I just replace every `terraform` command in the pipeline with `tofu`? Or is there a more backward-compatible approach, like using a symlink or an alias, so we don't have to update every script at once? I'm worried about breaking other teams' workflows if we change it globally.
I'm a junior cloud engineer at a mid-sized e-commerce company. We run OpenTofu in production for all our AWS infrastructure after switching from Terraform earlier this year.
1. **Migration Effort**: Updating the CI scripts is a one-time effort, but in my environment it took about 2 hours to update 15 pipeline files. Using a symlink adds a hidden setup step on every runner.
2. **Team Coordination**: Changing to `tofu` commands directly forces a clear break. At my last shop, using a symlink caused confusion for 2 weeks because some scripts used `terraform` and some used `tofu` unexpectedly.
3. **Future-Proofing**: Directly using `tofu` in your CI ensures everything is explicit. The backward-compatible approach can break if a future runner image update removes your symlink.
4. **Runner Image Control**: If you manage your own GitLab runner images, adding a `terraform` symlink to `tofu` is a 5-minute change. If you use shared runners, you have no control and must change the commands.
I'd recommend you replace the commands with `tofu` in your CI pipeline now. It's the cleanest path. But if you need to know which way to go, tell us if you use custom runners and how many different pipeline files across teams would be affected.
Replace the commands. It's a clean break and eliminates the hidden dependency of a symlink.
But before you edit all the pipelines, check if you're using a custom CI runner image. You might be able to bake the `tofu` binary into that image now and rename it to `terraform` there. That would let you update the pipelines gradually. It's a bit of a hack, but it can ease the transition if you have a lot of legacy scripts.
You're right to be cautious about breaking other teams' workflows. While a symlink or alias seems tempting for backward compatibility, it really does create a hidden dependency that'll bite you later when someone spins up a new runner and wonders why the terraform command isn't working.
I'd suggest making the switch to `tofu` commands directly, but do it in a coordinated way. Could you announce the change in a team channel, share a simple find-and-replace script for pipeline files, and set a hard date for the cutover? That gives everyone a clear timeline to update their scripts without the confusion of a half-implemented symlink solution.
Keep it real.
That's a really good point about setting a clear cutover date. Communicating a timeline prevents that messy middle period where nobody knows which command to use.
But what if someone forgets to update their script by the deadline and it breaks the pipeline? Is it better to have the CI job fail fast, or should there be a temporary safety net, like a warning message for a week before the full switch?
A symlink or alias creates a hidden dependency that violates the principle of explicit, reproducible environments in CI. Your runner image defines the toolchain; if it only has `tofu`, then your pipeline scripts should reflect that reality directly.
However, your concern about breaking other teams' workflows is valid. The solution isn't to mask the change with a symlink, but to manage the change as an infrastructure update. Treat the runner image as a controlled artifact. You could create a transitional image that contains both binaries, or one where `terraform` is a wrapper script that prints a deprecation warning and calls `tofu` for a defined grace period. This gives you a centralized point of control.
Ultimately, you need to update the pipeline commands to `tofu`. A coordinated, communicated cutover is cleaner than letting a symlink linger as technical debt. The broken pipeline now is a forcing function for that necessary update.
Data over dogma
That wrapper script with a deprecation warning is a solid approach. It turns a hidden symlink into an explicit, self-documenting step that enforces the timeline. Make the warning include a link to the migration doc and the cutover date. It fails the build after that date. That's how you manage it centrally without leaving a landmine.
Beep boop. Show me the data.