Migrate From HCP Terraform to Terrakube
Moving from HCP Terraform to Terrakube is mostly a state move plus some bookkeeping. Terraform state is a standard file, Terrakube accepts the same cloud block and remote backend, and its docs describe two migration paths: a Workspaces Importer in the UI and the Terraform CLI (Terrakube migration guide). This guide walks through both, plus the parts neither path covers. Commands and API fields come from the Terrakube docs and source at release 2.33.3 and from HashiCorp's API docs, checked October 23, 2026.
Quick answer
| Step | What to do |
|---|---|
| 1. Inventory | List workspaces, execution mode, VCS repo, variables, variable sets and policies |
| 2. Back up state | terraform state pull > backup.tfstate per workspace |
| 3. Prepare Terrakube | Organization, VCS connection, variable collections |
| 4a. Import (many workspaces) | Workspaces Importer with an HCP Terraform API token |
| 4b. Or move one by one | Change the cloud block hostname, terraform login, terraform init -migrate-state |
| 5. Policies | Rewrite Sentinel in Rego; adapt existing HCP OPA policies |
| 6. Verify | terraform plan must report no changes |
| 7. Cut over | Update CI tokens, lock the old workspace |
Sources: Terrakube migration guide, HCP Terraform workspaces API, checked October 23, 2026.
Step 1: Inventory your workspaces
Before touching anything, write down what each workspace has. The HCP Terraform API lists workspaces per organization at GET /organizations/:organization_name/workspaces, paginated, with attributes such as execution-mode, terraform-version, working-directory, vcs-repo-identifier and resource-count (Workspaces API). With a user or team token:
export TFC_TOKEN=... # HCP Terraform user or team token
ORG=your-org
page=1
while [ -n "$page" ]; do
resp=$(curl -s -H "Authorization: Bearer $TFC_TOKEN" \
"https://app.terraform.io/api/v2/organizations/$ORG/workspaces?page%5Bsize%5D=50&page%5Bnumber%5D=$page")
echo "$resp" | jq -r '.data[] | [.id, .attributes.name, .attributes["execution-mode"],
.attributes["terraform-version"], (.attributes["vcs-repo-identifier"] // "cli"),
.attributes["resource-count"]] | @tsv'
page=$(echo "$resp" | jq -r '.meta.pagination["next-page"] // empty')
done > workspaces.tsv
For each workspace, also note:
- Variables, including which are sensitive. HCP Terraform does not show a sensitive value again after it is written (workspace variables API), so find those values in your secrets manager now.
- Variable sets attached to the workspace. In Terrakube the equivalent is a variable collection.
- Policy sets, Sentinel or OPA.
- Execution mode. Terrakube's CLI-driven docs say all Terraform operations currently run remotely in Terrakube (CLI-driven workflow), so a workspace in local execution mode will start running remotely after the move. Its cloud credentials then have to live in Terrakube variables.
- Run triggers, notifications and agent pools, which neither path copies.
Step 2: Back up every state file
Both migration paths copy state, but keep your own copy first. From an initialized working directory, terraform state pull prints the current state to stdout (terraform state pull):
terraform state pull > backup-$(date +%F).tfstate
Without a working directory, use the API. GET /workspaces/:workspace_id/current-state-version returns a hosted-state-download-url for the raw state (state versions API):
WS_ID=ws-XXXXXXXXXXXXXXXX
url=$(curl -s -H "Authorization: Bearer $TFC_TOKEN" \
"https://app.terraform.io/api/v2/workspaces/$WS_ID/current-state-version" \
| jq -r '.data.attributes["hosted-state-download-url"]')
curl -sL -H "Authorization: Bearer $TFC_TOKEN" "$url" -o "$WS_ID.tfstate"
Record the serial and lineage from each file. You will compare them after the move.
Step 3: Prepare Terrakube
- Create the organization. Workspace names can stay the same.
- Connect your VCS provider in the organization settings if you have VCS-driven workspaces. The importer needs this connection before it can import them.
- Recreate each variable set you rely on as a variable collection under Organization Settings, Variable Collections (Terrakube variable collections). Note the precedence: workspace variables win, then organization global variables, then collections in priority order. A collection never overrides a workspace or global variable with the same key.
- Create an API token for later use in CI.
Step 4a: Use the Workspaces Importer
For more than a handful of workspaces, start here. In the organization's workspace list, click Import Workspaces, choose Terraform Cloud or Terraform Enterprise, pick the workflow type, select the VCS connection for VCS workflows, and enter your API token (plus the host URL for Terraform Enterprise). Then select the workspaces to import. The docs say the importer copies "name, description, Terraform version, execution mode, variables, tags and the current state". If your workspaces use more than one workflow type, run the importer once per type (Terrakube migration guide).
Two details from the importer's source at 2.33.3 (WorkspaceService.java, Import.tsx) that the docs do not spell out:
- Sensitive variables. HCP Terraform does not return their values, so the importer asks you for them. A sensitive variable you do not select is discarded. One you select but leave empty is imported and marked incomplete, and runs cannot start until you fill it in.
- Variable sets. The importer lists the variable sets attached to each workspace and lets you map each one to an existing Terrakube variable collection. Create the collections first (Step 3).
The importer reads the state's JSON form as well. HCP Terraform only provides that for state written by Terraform 1.3 or later; without it the importer warns that plans and applies still work but the UI cannot show the JSON state.
Step 4b: Or move workspaces with the CLI
For a few workspaces, or when you want to control each one, change the configuration and let Terraform move the state. Before:
terraform {
cloud {
organization = "your-org"
workspaces {
name = "networking-prod"
}
}
}
After, with hostname set to your Terrakube API host. Terrakube's docs stress that hostname is required, since the default is app.terraform.io:
terraform {
cloud {
hostname = "terrakube-api.example.com"
organization = "your-org"
workspaces {
name = "networking-prod"
}
}
}
Terrakube's docs show name and list-style tags = ["networking", "prod"] in the workspaces block. If your HCP configuration uses project or map-style tags, switch to one of those forms. The backend "remote" block works too, with the same three settings.
Then log in to both hosts and migrate:
terraform login app.terraform.io # if your session has expired
terraform login terrakube-api.example.com
terraform init -migrate-state
Terraform reads the state from HCP Terraform and writes it to the Terrakube workspace. Terrakube's docs also recommend a .terraformrc with plugin_cache_dir and disable_checkpoint = true for this path. If you start from a local state file instead, run terraform init and then terraform state push backup.tfstate.
This path moves state only. Recreate variables in the Terrakube UI, API or CLI, and reconnect the VCS repository if the workspace is VCS-driven.
Step 5: Policies
Terrakube runs OPA policies through templates, using a policy folder in your extensions repository (Terrakube OPA). It does not run Sentinel, so Sentinel policies have to be rewritten in Rego.
If you already use OPA policy sets in HCP Terraform, the Rego carries over with one change to check: HCP Terraform passes a combined document with plan data under input.plan and run data under input.run (HCP OPA policies), while Terrakube's example reads the plan JSON at the input root (input.resource_changes). Rules that use input.run need another source for that data. More in OPA policies in Terrakube.
Step 6: Verify with a plan
Run a plan against the Terrakube workspace before anything else:
terraform plan
You want "No changes. Your infrastructure matches the configuration." Then pull the state back and compare it with your backup:
terraform state pull | jq '{serial, lineage, n: (.resources | length)}'
jq '{serial, lineage, n: (.resources | length)}' backup-2026-10-23.tfstate
Same lineage and resource count means the right state arrived. If the plan shows changes, the usual cause is a missing or different variable, or a different Terraform version in the workspace settings. Do not apply until the plan is clean. Then do one full plan and apply cycle on a low-risk workspace before moving the rest.
Step 7: Cut over
- CI. Pipelines that use
TF_TOKEN_app_terraform_ioneed a token for the new host. The variable name is the hostname with dots replaced by underscores, and hyphens can be written as double underscores (CLI config). Forterrakube-api.example.comthat isTF_TOKEN_terrakube__api_example_com. - Old workspace. Lock it so nobody applies against stale state:
POST /workspaces/:workspace_id/actions/lockin the HCP API, or the lock setting in the UI. Keep it until you are confident, then delete it. - Webhooks. Make sure the HCP Terraform VCS connection no longer triggers runs on the same repository.
Doing this on Kevra Infra
The steps are the same on Kevra Infra, which runs the open-source Terrakube as a managed service. Use your instance's hostname in the cloud block and in terraform login; there are no servers, database or storage to set up on your side. Your state stays standard Terraform state. Plans start at USD 39 per month with one organization. Kevra-specific guides are in the Kevra Infra docs.
Related: HCP Terraform's 500-resource free tier, Hosted Terrakube, HCP Terraform alternatives, connecting GitHub and GitLab to Terrakube, Terrakube with OpenTofu.
FAQ
Can I migrate from HCP Terraform to Terrakube without losing state?
Yes. Both the Workspaces Importer and terraform init -migrate-state copy the current state into Terrakube. Back up each state with terraform state pull first, and confirm with a terraform plan that shows no changes.
Does the Terrakube Workspaces Importer copy variables?
Yes. It copies workspace variables, tags, Terraform version, execution mode and current state. HCP Terraform does not return sensitive values, so the importer asks you to enter them; skipped ones are discarded and empty ones block runs until filled in.
Do I need to change my Terraform code?
Only the cloud block (or backend "remote"): add hostname with your Terrakube API host and keep organization and workspaces. Resources and modules stay the same.
What happens to my Sentinel policies?
Terrakube does not run Sentinel. Rewrite them in Rego for OPA. Existing HCP Terraform OPA policies mostly carry over, but they read plan data from input.plan, while Terrakube's example reads it from the input root.
What about HCP Terraform variable sets?
Terrakube's equivalent is a variable collection. Create the collections first; the importer can then map each variable set attached to a workspace to a collection.
Can I migrate to OpenTofu at the same time?
You can, since Terrakube runs Terraform or OpenTofu, but change one thing at a time. Move the platform first, get a clean plan, then switch the tool.
Migrate to hosted Terrakube
Kevra Infra runs the open-source Terrakube platform for Terraform and OpenTofu, so you move your workspaces, not your servers. Plans start at USD 39 per month. Want to try it first? Ask us for a trial.
Terrakube is a trademark of its owners. Kevra is not affiliated with, endorsed by or sponsored by the Terrakube project. Kevra Infra runs the open-source Terrakube project (Apache-2.0) as a managed service.