---
title: "Migrate state to StackShip"
description: "Move local or external Terraform Cloud state through OpenTofu's native cloud contract."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.stackship.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate state to StackShip

StackShip keeps raw state accessible to authorized customers so an existing
workspace can move in or out without a private migration API. Migration uses
ordinary OpenTofu cloud commands and the same authenticated, locked,
lineage-aware state manager used by normal operations.

Migrate one workspace at a time. Freeze writes in the source before changing
custody.

## Preconditions

- Create the destination organization, project, and workspace.
- Use the same or a compatible exact OpenTofu version.
- Recreate non-sensitive variables and retrieve sensitive values from their
  original secret manager; write-only values cannot be exported from most
  control planes.
- Configure and verify destination credential profiles.
- Install the GitHub App if the destination will use GitHub source runs.
- Record the source lineage, serial, outputs, and resource addresses.

StackShip runs OpenTofu only. If the source applied with Terraform, validate
the state and provider-lock compatibility with the exact destination OpenTofu
version before transferring custody.

## Native cloud configuration

Authenticate interactively:

```bash
tofu login stackship.run
```

Automation may instead use an explicitly scoped service-account token in:

```text
TF_TOKEN_stackship_run
```

From the destination workspace dashboard, open **CLI access** in the
workspace section navigation. Choose **State migration**, select a bounded expiry, create
the token, and put its one-time plaintext in an approved secret manager. That
preset grants only `workspace:read`, `state:outputs:read`, `state:read`,
`state:write`, `state:lock`, and `state:recover`, bounded to the selected
project and workspace. OpenTofu marks its native state handoff as a forced
write, so `state:recover` is required for this preset. StackShip still enforces
the workspace boundary, lock ownership, upload checksum, and state validation.

Expose it to the OpenTofu process without writing it into configuration:

```bash
export TF_TOKEN_stackship_run='<service-account-token>'
```

Configure the destination:

```hcl
terraform {
  cloud {
hostname     = "stackship.run"
organization = "example"

workspaces {
  name = "network-foundation"
}
  }
}
```

Use the destination workspace's `cloud_name` shown by **CLI access**.
This is separate from its human display name and is unique within the
organization.

`stackship.run` serves OpenTofu discovery and advertises the API compatibility
endpoint at `https://api.stackship.run/api/v2/`.

Do not configure the generic HTTP backend. StackShip implements the native
cloud/TFE-compatible contract.

During a closed launch, native cloud access is available only to an approved
destination organization and workspace pair. The unauthenticated
`/api/v2/ping` compatibility probe remains available, but organization-wide
requests and ownership-unresolved requests return a generic retryable
`503 service_unavailable`. A valid configuration-upload, state-upload, or log
capability does not bypass this boundary: StackShip resolves the capability's
owning workspace and applies the same exact-pair check before consuming it.
Invalid cloud credentials still return `401 invalid_token`.

Confirm the exact destination workspace is available before a
migration window. A generic 503 intentionally does not say whether the
workspace exists or whether it is available; wait 60 seconds and retry only
after confirming availability out of band.

## Move local state

Start in the directory that owns the existing local `terraform.tfstate`.
Create a protected backup and record its checksum before editing the cloud
block.

```bash
cp terraform.tfstate terraform.tfstate.pre-stackship
sha256sum terraform.tfstate.pre-stackship
tofu state pull > source-state.json
```

Add the cloud block, then run:

```bash
tofu init
```

OpenTofu prompts to migrate the existing state. Read the source and destination
names, then confirm.

OpenTofu 1.12.5 rebases metadata when this native-cloud flow targets an empty
workspace. It clears the local lineage and serial while refreshing the empty
destination, then sends StackShip a new lineage at serial `1`. StackShip stores
the lineage and serial that the client sends; it cannot recover the source
metadata after OpenTofu has replaced it.

The release browser covers both boundaries explicitly:

- the publication/read case creates fresh cloud state and reads it through the
  native API; it is not migration proof; and
- the local-to-cloud case starts with two local applies, confirms the native
  migration prompt, proves outputs and resources are preserved, and proves the
  rebased request, pulled state, and control-plane metadata agree.

The current release gate therefore does not prove preservation of the source
lineage or serial. Keep the protected source snapshot and record the mapping
from source metadata to the new destination metadata.

After migration:

```bash
tofu state pull > stackship-state.json
sha256sum stackship-state.json
tofu plan -refresh=false
```

Compare lineage, serial, outputs, and resource addresses before allowing a new
write. For an empty-workspace migration with OpenTofu 1.12.5, expect the
destination lineage to differ and its first serial to be `1`; outputs and
resource addresses must still match exactly.

## Move from Terraform Cloud or another cloud-compatible service

While the source configuration and credential are still active:

```bash
umask 077
tofu state pull > source-state.json
```

Keep the source workspace locked or otherwise frozen. In a separate clean
directory, configure the StackShip cloud hostname, organization, workspace, and
credential without copying the source backend's local initialization data.
Initialize that destination without applying, then push the protected
snapshot:

```bash
tofu init -input=false
tofu state push source-state.json
tofu state pull > stackship-state.json
```

The upload must pass tenant authorization, the workspace writer reservation,
100 MB plaintext limit, checksum verification, lineage rules, and serial
ordering before it becomes current.

The release browser proves this sequence with a serial-greater-than-one source
containing a real built-in resource, a separate clean destination directory,
plain cloud initialization, and `tofu state push` without `-force`. OpenTofu
1.12.5 still replaces the source lineage and serial before the first upload to
an empty destination. The pulled state and StackShip control-plane record share
the new lineage at serial `1`; the non-empty resource and output remain
structurally identical at the state model boundary. The release case uses a
streaming loopback probe to observe `data.attributes.force` as `false` on the
state-create request. Its token also deliberately lacks `state:recover`, so a
regression to `force: true` would fail closed before StackShip reserved an
upload.

This is evidence that the documented export/import path preserves state
content, not source metadata. Record the source-to-destination lineage mapping,
rehearse against a non-production workspace, and retain the protected source
export until the destination has completed a clean plan and apply.

## Recreate the surrounding control plane

State migration does not copy:

- GitHub App installations or source settings;
- variable values;
- cloud trust relationships;
- policy source or attachments;
- organization members; or
- prior run and audit history.

Recreate those deliberately. StackShip's policy feature implements its
documented Rego contract, not Sentinel compatibility. Rewrite Sentinel policy
as Rego before attaching it. There is no bulk importer.

## Cut over

Before the first destination apply:

1. prove only StackShip can write the state;
2. run a non-applying plan and investigate every unexpected difference;
3. review the attached policy result;
4. confirm the expected predecessor lineage and serial;
5. approve the exact current evidence; and
6. keep the protected source snapshot until the destination has completed a
   clean apply.

Never let two control planes apply against the same infrastructure.

## Move out again

An authorized principal can download a state version from the dashboard or
native API, or use `tofu state pull` through the cloud protocol. Raw state
downloads are audited and streamed; StackShip does not make the underlying R2
object public.

## Related pages

- [State and native cloud](/workspaces/state)
- [Variables and secrets](/workspaces/variables)
- [GitHub source connections](/workspaces/vcs)
- [Run approvals](/runs/approvals)

Source: https://docs.stackship.run/start/migrate/index.mdx
