Migrating off Terraform Enterprise to OpenTofu and Spacelift

By October 26, 2026DevOps
Migrating off Terraform Enterprise to OpenTofu and Spacelift

Leaving Terraform Enterprise is three migrations wearing one name. Here is the sequence we run, the phase that always slips, and the billing detail that catches finance by surprise.

Pre-publish checklist for this draft
Every [[TOKEN]] below is an unverified client figure. Replace each with a number you can defend, or delete the sentence. Then delete this box and the .editor-note and .placeholder CSS rules. Pricing figures cited from public sources are dated in the text and should be re-checked against the vendor pricing pages on the day you publish.

A platform lead at an enterprise client forwarded us a renewal quote and a one-line question: is there a way out of this that does not take a year?

The quote itself was not the interesting part. The interesting part was that his team had already tried twice. Both attempts stalled in the same place, and it was not the place anyone expected. They had treated leaving Terraform Enterprise as one project. It is three, and only one of them is easy.

This is the sequence we now run for platform teams moving onto OpenTofu and Spacelift, written from engagements rather than from the vendor documentation. If you are evaluating rather than executing, the first two sections will tell you whether the move is worth scoping at all.

Why platform teams are leaving Terraform Enterprise

The licensing story is well known. In August 2023, HashiCorp moved Terraform from MPL 2.0 to the Business Source License; a fork called OpenTofu appeared within weeks under the Linux Foundation, and in April 2025 OpenTofu was accepted into the CNCF. IBM closed its acquisition of HashiCorp on February 27, 2025, and Terraform Enterprise is now sold through IBM.

That history is the backdrop, not the trigger. The trigger is almost always the meter.

HCP Terraform and Terraform Enterprise bill on resources under management. A managed resource is any resource in a managed state file where mode is "managed". Counting starts at the first apply that touches it and continues until it is destroyed or removed from management. Billing is hourly, and the charge is based on the peak count within the hour rather than the average. Create five hundred resources and destroy two hundred of them inside the same hour, and you are billed on seven hundred.

As of 2026, the published HCP Terraform rates are 0.10, 0.47 and 0.99 US dollars per managed resource per month on the Essentials, Standard and Premium tiers, with the first five hundred resources free. Terraform Enterprise, the self-managed distribution, is quote-based. Confirm all of these against the current pricing page before you build a business case, because the model has been revised repeatedly and now routes through IBM.

The structural objection matters more than the rate card. Resources under management penalise the practice the industry spends its time recommending. Every resource you bring under code adds cost, which quietly rewards leaving things out of Terraform. We have watched teams keep shadow infrastructure out of state for budget reasons and then discover the drift eighteen months later. That is the real bill.

OpenTofu and Spacelift answer two different questions

This is where both of our clients’ earlier attempts died. The team kept asking which tool replaces Terraform Enterprise, and there is no single answer, because Terraform Enterprise is doing two jobs that are normally separate products.

OpenTofu is an engine decision. It is the MPL 2.0 licensed fork of Terraform, governed by the Linux Foundation and accepted into the CNCF in April 2025. Most Terraform configurations run on it unchanged, and the same provider binaries serve both engines. Since the fork, it has shipped capabilities the open Terraform binary does not have: client-side state encryption in 1.7, early variable evaluation in 1.8, provider iteration with for_each and the -exclude flag in 1.9, OCI registry support for modules and providers in 1.10, and in 1.12, released May 14, 2026, a dynamic prevent_destroy that can finally be wired to a variable so production and development workspaces can share module code without forking it. Version 1.13 followed in September 2026 with early work on built-in linting.

Spacelift is a control plane decision. It runs the engine, holds the run queue, enforces policy, and connects to your VCS. It is not Terraform-specific: the same workflow covers Terraform, OpenTofu, Terragrunt, Pulumi, CloudFormation, Kubernetes, and Ansible, and a stack can be switched between engines by changing its configuration rather than by rebuilding the pipeline.

opentofu-spacelift-two-decisions

The engine decision and the control plane decision are separable in principle. One quadrant is closed, which is what forces them to be made together.

The quadrant that does not exist is the whole problem. Terraform Enterprise will not execute OpenTofu. You cannot change the engine and keep the orchestration, which means the licensing decision drags the platform decision along with it, whether or not you were ready to make it. Sequence the work around that constraint instead of pretending the two are independent.

What actually moves, and what gets rebuilt

Terraform Enterprise bundles five jobs: the run queue, state storage and locking, policy enforcement, a private module registry, and the engine binary itself. Three of those genuinely move to a new owner. Two of them do not move at all; they get rebuilt, and that distinction drives the schedule.

What actually moves

Terraform Enterprise bundles five jobs. Three of them move to a new owner. Policy and the private module registry get rebuilt, which is where the schedule risk lives.

State custody is the change with the largest governance payoff and the smallest engineering cost. In the target architecture, state lives in a bucket in an account you control, with OpenTofu’s client-side encryption enabled, and the control plane holds a reference rather than the data. For clients in regulated industries, this alone has justified the project independently of the licence and the bill. We have had healthcare clients where the answer to “who can read a state file containing database endpoints and resource identifiers” was materially more important than the renewal number.

One thing to decide before anything else

Spacelift runs workers either on its infrastructure or in a private worker pool inside your VPC. If your plans need network reachability to private subnets, or your compliance posture requires that credentials never leave your account, choose the private pool on day one. Retrofitting it after fifty stacks exist is a second migration.

The six-phase sequence

The ordering below is deliberate. Policy work starts early and runs long, in parallel with everything else, because it is the only phase whose duration you cannot compress by adding automation

terraform enterprise migration phases

The six-phase sequence. Phases 2 and 3 overlap deliberately, and cutover is gated on plan equivalence rather than on a date.

Phase 1: inventory and freeze

Export every workspace, variable set, policy set, run task, team, and agent pool. Then freeze workspace creation in Terraform Enterprise. A moving source is the single most common reason these projects run long, because every new workspace created during the migration is a workspace nobody has mapped.

Spacelift ships a migration tool liftoffthat handles the export and the code generation for Terraform Cloud and Terraform Enterprise estates. It maps organizations and projects to Spacelift spaces and workspaces to stacks, and it renders the result as a nested OpenTofu module tree, so the migration arrives as reviewable code rather than as an opaque API run. Teams, agent pools, policies, and run tasks come across as audit-only records. They are inventory, not configuration. Read that sentence twice if you have a large policy estate, because it is telling you that the policy work is yours.

Phase 2: engine swap in a sandbox

Take a copy of state, point OpenTofu at it, and compare plans. Never apply in this phase.

<span class="cm"># work against a copy, never the live state</span>
terraform state pull > tfe-workspace.tfstate

<span class="cm"># same config, same providers, different binary</span>
tofu init -backend-config=sandbox.hcl
tofu plan -out=tofu.plan -lock=false

<span class="cm"># the gate is plan equivalence, not a successful run</span>
tofu show -json tofu.plan > tofu.json
terraform show -json terraform.plan > tf.json
diff <(jq -S '.resource_changes' tofu.json) <(jq -S '.resource_changes' tf.json)

Pin the engine version explicitly on every stack. Letting a control plane pick a default engine version is how you get a plan diff that nobody ordered three weeks after cutover.

Phase 3: policy rebuild

Covered in its own section below. Start it here, finish it whenever it is finished, and do not let it be the thing you discover in week ten.

Phase 4: stack generation and state custody

Generate the stack tree, stand up the state backend you own, and move state workspace group by workspace group. One detail trips up nearly everyone: Spacelift expects your configuration to declare no backend at all. An existing cloud or backend block will keep init failing until it is removed, and the error does not obviously say so.

<span class="cm"># remove this before the first Spacelift run</span>
<span class="kw">terraform</span> {
  <span class="kw">cloud</span> {
    organization = <span class="st">"acme-platform"</span>
    workspaces { name = <span class="st">"prod-network"</span> }
  }
}

<span class="cm"># OpenTofu state encryption, configured in the stack</span>
<span class="kw">terraform</span> {
  <span class="kw">encryption</span> {
    key_provider <span class="st">"aws_kms"</span> <span class="st">"prod"</span> {
      kms_key_id = <span class="st">"arn:aws:kms:us-west-2:…:key/…"</span>
      key_spec   = <span class="st">"AES_256"</span>
    }
    method <span class="st">"aes_gcm"</span> <span class="st">"prod"</span> {
      keys = key_provider.aws_kms.prod
    }
    state {
      method = method.aes_gcm.prod
    }
  }
}

Phase 5: parallel run

Both control planes watch the same repository and plan the same commits. Neither applies except the one you have designated. A workspace becomes eligible for cutover when the two plans agree twice in a row on unrelated commits, not when a date arrives. This is the only quality gate in the project that is cheap to run and expensive to skip.

Phase 6: cutover and decommission

Destroy nothing. You are removing resources from management, not from existence, and the two are very different operations with the same blast radius if you confuse them. Remove the workspaces from Terraform Enterprise so the meter stops counting, confirm the next invoice reflects the drop, and only then revoke the tokens.

Policy is the long pole, and it is a rewrite

Sentinel policies do not run on Spacelift. Spacelift uses Open Policy Agent with rules written in Rego, and no automatic translation exists. Every rule is rewritten by hand.

Worse, and better, the shape of the problem changes. Sentinel evaluates a plan at one gate. Spacelift attaches policies at six separate points across the run lifecycle: login, access, push, plan, approval and notification. So the rules are not just translated; they are redistributed, and a meaningful fraction of what you wrote as a plan time check is better expressed earlier, as an access rule or a push rule.

Policy is the long pole

Sentinel evaluates once, post-plan. Spacelift attaches OPA policies at six points in the run lifecycle, so rules are redistributed as well as rewritten.

A concrete example. A Sentinel rule that blocks untagged resources at plan time becomes, in Rego:

<span class="kw">package</span> spacelift

deny[msg] {
  r := input.terraform.resource_changes[_]
  r.change.actions[_] == <span class="st">"create"</span>
  not r.change.after.tags.cost_center
  msg := sprintf(<span class="st">"%s is missing cost_center"</span>, [r.address])
}

<span class="cm"># the part that has no Sentinel equivalent: require a second</span>
<span class="cm"># approver when the plan touches anything in the prod space</span>
<span class="kw">package</span> spacelift

approve {
  count(input.reviews.current.approvals) >= 2
}
approve {
  input.spacelift.stack.space != <span class="st">"prod"</span>
  count(input.reviews.current.approvals) >= 1
}

Treat this as its own workstream with its own owner and its own definition of done. In the engagements we have run, it consumes more calendar time than the state migration and the engine swap combined, and it is the only phase where a mistake is silent rather than loud. A broken plan fails visibly. A policy that no longer fires does not.

Five things that bite teams on the way through

What happens Why, and what to do
tofu init keeps failing on a clean repo A leftover cloud or backend block. Spacelift wants no backend declared. Remove it before the first run rather than debugging the init output.
The bill does not drop after cutover Resources under management are metered until the resource is destroyed or removed from management. Migrating state elsewhere does not by itself stop the old meter. Remove the workspaces, then verify on the next invoice.
A provider or module version cannot be resolved OpenTofu has its own registry. Most providers are present, and the binaries are shared, but some newer versions are published only to the HashiCorp registry. Inventory your provider pins in phase 1, not phase 4.
Private modules stop resolving The Terraform Enterprise private module registry does not come with you. Re-point module sources at your VCS or an OCI registry, which OpenTofu has supported since 1.10. Plan this as a code change across every root module.
Governance quietly regresses Run tasks, teams, and agent pools arrive from the migration tool as audit-only data. Somebody has to decide, deliberately, what the Spacelift equivalent of each one is. Nothing in the tooling will tell you that a control has gone missing.

How we scope the engagement

We size these projects on two numbers, and workspace count is not one of them.

  • Policy rule count and complexity. This predicts the timeline better than anything else, because it is the only work that does not parallelise well.
  • Root module count and module registry depth. This predicts how much code change the migration actually requires, as distinct from configuration change.

For the client described at the top, the estate was [[WORKSPACE_COUNT]] workspaces across [[CLOUD_ACCOUNTS]] cloud accounts, with [[POLICY_COUNT]] Sentinel rules and [[MODULE_COUNT]] private modules. The engine swap and state custody work ran [[ENGINE_WEEKS]] weeks. The policy rewrite ran [[POLICY_WEEKS]]. Parallel run overlapped both and added [[OVERLAP_WEEKS]] weeks of double running cost, which we timed to land before the renewal date rather than after it.

That overlap is a real line item, and it is the one finance is never told about. During parallel run, you are still paying the old meter in full while standing up the new platform. Scope it, name it, and time the final cutover against the contract date.

Where agentic development changes the arithmetic

Two parts of this migration are now substantially faster than they were a year ago, and both are mechanical transformation problems with a verifiable output, which is exactly the shape of work that agents handle well.

The first is the Sentinel to Rego rewrite. It is a translation task with a test oracle: for any given plan JSON, the old rule and the new rule should reach the same verdict. We run it as an agent loop with the plan corpus from phase 2 as the fixture set, and a human reviewing the rules that disagree rather than reading every rule. The second is module source re-pointing across dozens of root modules, which is a refactor with a compile check at the end.

liftoff It is built to be driven this way. Point an agent at the repository, and it installs the tool, runs the embedded walkthrough, and drives the migration step by step, with every step equally available to type by hand. That design choice is a good signal about where this category is going.

What does not change: the plan diffing gate, the decision about which governance controls need an equivalent, and the judgement call about what is safe to cut over. This is the division of labour our AI Development Lifecycle is built around. Agents do the transformation, engineers own the verification, and the gate stays human.

Frequently asked questions

Can Terraform Enterprise run OpenTofu? +

No. Terraform Enterprise executes the HashiCorp Terraform binary only. If you want OpenTofu as your execution engine, you also need a control plane that runs it, which is why the engine decision and the orchestration decision have to be made together.

Is OpenTofu a drop-in replacement for Terraform? +

For configuration and state, it is very close. OpenTofu forked from Terraform 1.5; most configurations run unchanged, and the same provider binaries serve both engines. The incompatibilities are concentrated in features specific to Terraform Cloud and Terraform Enterprise, in newer provider versions published only to the HashiCorp registry, and in the language features each project has added since the fork. If you are on Terraform 1.5 or earlier, move to OpenTofu 1.6 first and upgrade from there.

Do Sentinel policies work on Spacelift? +

No. Spacelift uses Open Policy Agent with policies in Rego, and there is no automatic translation from Sentinel. Spacelift also attaches policies at six points in the run lifecycle rather than at one post-plan gate, so rules are redistributed as well as rewritten. Budget this as the longest phase.

How do you move Terraform state out of Terraform Enterprise? +

Pull state from each workspace with the CLI, store it in a backend you own, such as an S3 bucket with native locking, and have the new stack initialize against it. Remove the existing cloud or backend block first, because Spacelift expects no backend declared in your configuration.

What does Terraform Enterprise cost compared with Spacelift? +

They meter different things. HCP Terraform bills on resources under management, per managed resource, metered hourly against the peak count in each hour, with published 2026 rates of 0.10, 0.47, and 0.99 US dollars per resource per month across Essentials, Standard, and Premium. Terraform Enterprise is quote-based and now sold through IBM. Spacelift prices on concurrency and workers, so the two models diverge as your resource count grows rather than as your team grows. Verify both against current pricing pages before modelling.

How long does a Terraform Enterprise migration take? +

The engine swap and state move are measured in days per workspace group and automate well. The policy rewrite and module registry re-pointing set the real schedule, and both scale with how much governance you have encoded rather than how many resources you manage. Inventory the policies first.

Raj Sanghvi

Raj Sanghvi is a technologist and founder of Bitcot, a full-service award-winning software development company. With over 15 years of innovative coding experience creating complex technology solutions for businesses like IBM, Sony, Nissan, Micron, Dicks Sporting Goods, HDSupply, Bombardier and more, Sanghvi helps build for both major brands and entrepreneurs to launch their own technologies platforms. Visit Raj Sanghvi on LinkedIn and follow him on Twitter. View Full Bio