

Terraform state doesn't stay put forever. Teams outgrow local state files, consolidate multiple backends into one, switch cloud providers, or decide it's time to move off a platform that's no longer working for them - HCP Terraform's free tier ending and its resource-based pricing is a common trigger these days. Whatever the reason, the state file itself is the part everyone's afraid to touch, because it's the only record of what Terraform thinks your infrastructure actually is.
The good news: Terraform has built-in tooling for this, and it's more forgiving than it looks once you understand which command does what. This guide covers the core migration methods and worked examples for the most common backend swaps, plus the specific paths for moving state into and out of env zero.
Why teams migrate state in the first place
A few scenarios come up repeatedly:
- Moving off local state. Local
terraform.tfstatefiles don't support locking or team access, so this is usually the first migration a growing team makes. - Switching cloud providers or regions. An S3 bucket in the wrong region, or a move from AWS to Azure, means the backend needs to move too.
- Consolidating state storage. Multiple teams standardizing on one backend, one bucket structure, or one access-control model.
- Leaving a platform behind. Cost, feature gaps, or workflow friction with a current remote-operations platform - migrating off Terraform Cloud is the version of this we see most often.
Each of these is a backend migration at the mechanical level, you're telling Terraform "the state lives somewhere new now," and asking it to either move the data or just start reading from the new location.
-reconfigure vs -migrate-state: know which one you need
Both flags apply when you change a backend block and run terraform init again. They do different things, and picking the wrong one is the single most common way people scare themselves during a migration:
terraform init -migrate-statecopies your existing state into the new backend. Use this when you want continuity - the new backend should end up with the same state your old one had.terraform init -reconfigureignores any existing state at the new location and just starts fresh with the new backend configuration. Use this when you're intentionally not carrying state over - for example, pointing at a backend that already has the correct state in it.
If you run -reconfigure when you meant -migrate-state, Terraform will think your infrastructure doesn't exist yet and may try to recreate it. Always default to -migrate-state unless you have a specific reason not to.
Method 1: terraform init -migrate-state
This is the standard path for most backend-to-backend moves. The pattern is the same regardless of which backend you're moving to:
- Update the
backendblock in your configuration to point at the new location. - Run
terraform init -migrate-stateand confirm the prompt. - Run
terraform plan— you should see no changes, or only trivial ones. Anything more than that means something in the migration didn't line up.
Local to Amazon S3:
//hcl
terraform {
backend "s3" {
bucket = "my-org-tfstate"
key = "prod/terraform.tfstate"
region = "us-east-1"
}
}After adding this and running terraform init -migrate-state, Terraform detects the backend change, copies your local state into the S3 bucket, and confirms the new backend is active. From that point on, terraform plan reads from S3.
Local to Azure Storage Account:
//hcl
terraform {
backend "azurerm" {
resource_group_name = "tfstate-rg"
storage_account_name = "myorgtfstate"
container_name = "tfstate"
key = "prod.terraform.tfstate"
}
}Same flow: the storage account and container need to exist first (with the right access permissions for whoever's running init), then terraform init -migrate-state handles the copy.
Method 2: manual state pull / state push
Sometimes you can't rely on -migrate-state — the target backend doesn't exist as Terraform-managed config yet, or you need to inspect and edit the state before it lands somewhere new. In that case:
terraform state pull > backup.tfstateUpdate the backend configuration, run terraform init (with -reconfigure if the new backend has no state of its own yet), then push the state you pulled:
terraform state push backup.tfstateThis is also the safety net for any migration - always pull a backup before you touch the backend block, regardless of which method you're using.
Reversing direction: remote back to local
Migrations aren't always toward more centralization. Dropping back to local state - for a small project, a one-off environment, or a deliberate architecture change — works the same way in reverse:
terraform state pull > terraform.tfstateRemove the backend block entirely (an unconfigured backend defaults to local), then run terraform init -migrate-state again. Terraform copies the remote state down into a local file and you're back to terraform.tfstate living next to your configuration.
If you're using Terragrunt
Terragrunt wraps this same Terraform mechanism but manages it per-unit via remote_state blocks, as covered in our Terragrunt tutorial. Two commands matter here:
terragrunt backend bootstrap— creates the backend resources (bucket, table, etc.) if they don't exist yet.terragrunt backend migrate old-unit new-unit— moves state between two Terragrunt-managed units.
If you're migrating many workspaces at once, Terragrunt's per-unit structure makes it easier to script the process across all of them rather than repeating the manual steps unit by unit.
Migrating into env zero
Everything above is backend-agnostic, it works whether the destination is env zero or anything else. But if env zero is the destination, there's a more direct path, and which one to use depends on where you're coming from.
env zero supports a few different ways of storing state depending on your setup (covered in more detail here) which affects what "moving state into env zero" actually means for you: bringing your existing external backend along unchanged, or pointing your environment at env zero's own remote backend.
Coming from Terraform Cloud or Terraform Enterprise
This is the most common migration path into env zero, and there's a dedicated tool for it: the env zero Migration Wizard, found under Organization Settings → Migration. Rather than rebuilding your Terraform Cloud or Terraform Enterprise setup by hand, the wizard connects to your organization with a read-only API token, scans your existing workspaces, and recreates them as env zero environments - carrying over variables, variable sets, VCS configuration, project hierarchy, and state in the process. For organizations with many workspaces, it supports a staged migration: move a handful of workspaces first, validate them in env zero, then continue migrating the rest whenever you're ready. A final go-live step locks the source Terraform Cloud/Enterprise workspaces and activates the corresponding env zero environments, so there's no window where both platforms are trying to run deployments at once.
A few things the wizard doesn't carry over automatically, worth planning for post-migration: private module registry contents, Sentinel/OPA policy definitions, run triggers and workspace dependencies, team permissions and RBAC, notification integrations (Slack, email), and SSH keys for repository access. These are all quick to reconfigure directly in env zero once your environments are in place.
This is also the migration path Elevate took after running into concurrency limits and unpredictable resource-based pricing as their infrastructure scaled. Paul Trout, Sr. Cloud Architect at Elevate, described migration as "a very scary word, especially when it's the core piece of infrastructure that drives your production releases" - but with env zero's migration tooling, the team completed the switch, alongside a parallel move from Terraform to OpenTofu, within a few weeks.
If you'd rather migrate one workspace at a time, manually, the process uses Terraform's native cloud block rather than a backend block, since that's what TFC/TFE-style remote state expects:
//hcl
terraform {
cloud {
hostname = "backend.api.env0.com"
organization = "<YOUR_ORGANIZATION_ID>.<YOUR_PROJECT_ID>"
workspaces {
name = "my-prod-resource"
}
}
}The manual path in short:
- Add a
TF_TOKEN_app_terraform_io(orTF_TOKEN_your_tfe_hostfor a custom hostname) environment variable with your TFC/TFE token, at the organization or project level if you're doing this for more than one workspace. - Add
ENV0_SKIP_WORKSPACE=true— without it, env zero will error on workspace names it doesn't recognize when a TFC/TFE-style remote backend is in play. - In env zero, set the environment's Workspace Name to match your existing TFC/TFE workspace name exactly (the name, not the
ws-ID). Don't enable "Use env zero remote Backend" yet. - Run the environment. You should see no changes — this confirms env zero is reading the same state TFC/TFE already had.
- Now go to Environment → Settings, check "Use env zero remote Backend", and save.
- Redeploy. Terraform will report the backend configuration changed and ask to migrate state — confirm, and env zero takes over as the backend from here.
- Optional cleanup: remove the
TF_TOKEN_*variable and anyTF_CLI_ARGS_inityou added for the transition, and drop thecloudblock from your config if you don't need the remote-plan features it enables.
Coming from a self-managed backend (S3, Azure, GCS, etc.)
If your state already lives in a backend you manage yourself, you have a choice: keep using it exactly as-is (env zero doesn't require you to move state storage at all), or move it into env zero's own remote backend. To move it:
//hcl
terraform {
cloud {
hostname = "backend.api.env0.com"
organization = "<YOUR_ORGANIZATION_ID>.<YOUR_PROJECT_ID>"
workspaces {
name = "<YOUR_WORKSPACE_NAME>"
}
}
}Running terraform init -migrate-state against this configuration triggers the standard Terraform migration flow — it'll report it's migrating from your existing backend to the cloud backend and ask for confirmation. Say yes, and env zero automatically detects the incoming state and creates a matching environment for you, named after your workspace. From there, just double-check the VCS details point at your actual repository rather than a placeholder.
If you're coming from Atlantis specifically, there's a dedicated walkthrough that covers the same remote-backend approach in that context.
Migrating state out of env zero
The reverse works the same way any backend-to-backend migration does: remove env zero's backend configuration, add the backend block for wherever you're moving to, and run terraform init -migrate-state. Confirm with terraform plan that nothing unexpected shows up, and if needed, terraform state push backup.tfstate to be explicit about it. Once the state's confirmed in its new home, the env zero environment can be marked inactive.
FAQ
Do I need to migrate state and workspaces at the same time?
Not necessarily - you can migrate state independently of workspace configuration, but if you're moving away from TFC/TFE, the Migration Wizard handles both together and is less error-prone than doing each by hand.
What happens if terraform plan shows changes right after a migration?
Stop and investigate before applying anything. A clean migration should show no changes (or only cosmetic ones like formatting). Unexpected changes usually mean a resource address, provider version, or variable value doesn't match between the old and new setup.
Can I use my own S3 or Azure backend and still get env zero's other features?
Yes, env zero's own remote backend is optional. Environments can keep using an externally managed backend while still getting env zero's governance, cost visibility, and drift detection on top.
Is the Migration Wizard safe to run against production workspaces?
The wizard is designed for exactly that use case, including a staged rollout so you can validate before cutting production traffic over. The standard precautions still apply: pull a state backup first, and verify with terraform plan before treating the migration as complete.
For more on state fundamentals (locking, drift, and the structure of the state file itself) see our guide to the Terraform state file and Terraform best practices for state management.
.webp)

![Using Open Policy Agent (OPA) with Terraform: Tutorial and Examples [2026]](https://cdn.prod.website-files.com/63eb9bf7fa9e2724829607c1/6a4f2974e259132365f900ea_69d6a3bde2ffe415812d9782_post_th.png)