Migrate from Local to Remote State
Migrate from local to remote state — Terraform foundations.
Focus: migrate from local to remote state
You've been managing your Terraform infrastructure with a terraform.tfstate file sitting in your working directory. It works — until a teammate runs terraform apply from their laptop, or your CI machine overwrites the state you carefully updated. Suddenly your infrastructure is out of sync, and you're left wondering which state file is the 'real' one. This is the classic pain of local state: it locks you to one machine, blocks collaboration, and puts your entire environment at risk if that file is lost or corrupted. In this lesson, you'll learn how to migrate from local to remote state, unlocking team workflows, state locking, and disaster recovery — a foundational step for any serious Terraform project.
The problem this lesson solves
Local state is a ticking time bomb for any team using Terraform. Here's why:
- Single point of failure: If your laptop dies or the directory is cleaned, your
terraform.tfstatefile — and the knowledge of what resources Terraform manages — vanishes. You'd have to recreate it manually or riskterraform destroydeleting infrastructure it no longer tracks. - No collaboration: Two people can't run
terraform planorapplyat the same time without risking conflicts. Each writes to their own local file, and merging those files is a nightmare. - No state locking: Terraform can't prevent two users from running
applysimultaneously. The result? Race conditions that corrupt infrastructure. - No audit trail: You can't see who changed what and when — essential for compliance and debugging.
Migrating to remote state solves all of this by storing your state in a shared, durable backend like AWS S3, Azure Storage, or HashiCorp Cloud. The state file becomes a single source of truth that your whole team can access securely, with built-in locking and versioning.
The real challenge isn't whether to migrate — it's how to do it safely without breaking your existing infrastructure. If you simply change your backend configuration and run terraform init, Terraform asks you whether to copy your existing local state. Get that wrong, and you could end up with two divergent state files or, worse, an orphaned remote state that doesn't match reality. This lesson walks you through the migration process step by step, so you can switch from local to remote state with confidence.
Core concept / mental model
Think of Terraform state as the source of truth for your infrastructure. It maps your configuration to the real-world resources — the instance IDs, IP addresses, and ARNs that Terraform created. Without state, Terraform would have to re-inspect every resource on every run, which is slow and fragile.
When you use local state, that source of truth lives on your machine. Migrating to remote state is like moving your company's records from a personal filing cabinet to a shared, secure vault. The vault (the backend) is accessible to everyone with the right key, has a lock so only one person can edit at a time, and keeps historical versions in case of accidental changes.
Here's a quick breakdown of the core concepts:
- Backend: A storage location for state — e.g.,
s3for AWS,azurermfor Azure, orlocalfor filesystem. Each backend has its own configuration settings. - Remote state: State stored outside your working directory — typically in cloud storage. It enables locking and collaboration.
- State locking: A mechanism that prevents concurrent writes. The backend implements it via locks (e.g., S3 bucket + DynamoDB table).
- State migration: The process of copying the existing state from one backend to another, usually triggered during
terraform init.
Pro tip: The migration is not a manual copy of the JSON state file. You should never copy or edit
terraform.tfstateby hand. Instead, let Terraform handle the transition cleanly during initialization.
How it works step by step
The migration process follows a deliberate sequence to avoid data loss or inconsistency. Here's the high-level flow:
- Prepare the backend — Create the remote storage (e.g., an S3 bucket) and any supporting resources (like a DynamoDB table for locking).
- Update your configuration — Add a
backendblock to your Terraform configuration, specifying the remote backend type and necessary parameters. - Run
terraform init— Terraform detects the backend change and prompts you to copy the existing local state to the new remote location. - Verify the migration — Run
terraform planto confirm Terraform sees the same infrastructure (no unexpected changes) and that state is stored remotely. - Commit your changes — Share the updated backend configuration with your team so everyone uses the same remote state.
Step 1: Prepare the backend
For an AWS S3 backend, you need a bucket and typically a DynamoDB table for state locking. You can create these manually via the AWS console or CLI, but since this is Terraform, we'll do it with Terraform — but carefully. If you create the bucket in the same configuration that uses the backend, you'll hit a chicken-and-egg problem. Instead, create it in a separate bootstrap configuration or apply it before switching the backend.
Step 2: Update your configuration
You'll define the backend in a terraform block. For S3, it looks like:
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
The key is the path where the state file will be stored inside the bucket. It's a good practice to use a per-environment prefix (e.g., prod/, staging/).
Step 3: Run terraform init
This is the magic moment. When you run terraform init after changing the backend, Terraform asks:
Do you want to copy existing state to the new backend?
Pre-existing state was found while migrating the previous "local" backend to the newly configured "s3" backend.
Enter a value:
Type yes to copy. Terraform then uploads your state to the remote location.
Pro tip: You can automate this with
-input=falseand-force-copyif you're confident. But for a first migration, always do it interactively so you can review.
Step 4: Verify
After migration, run terraform plan. If everything is correct, Terraform should show no changes (or only expected ones). To confirm state is remote, check the output of terraform state list or look at the terraform.tfstate file (now just a pointer to remote state).
Step 5: Commit and collaborate
Once verified, commit your .terraform initialization and backend configuration changes. Share the backend settings with your team — they'll run terraform init and automatically use the same remote state.
Hands-on walkthrough
Let's put this into practice. We'll use a simple AWS S3 bucket as the remote backend.
Setup: Bootstrap the backend infrastructure
First, create a separate directory (e.g., infra-bootstrap) with a configuration that creates the S3 bucket and DynamoDB table. This keeps the backend creation separate from your main infrastructure.
# infra-bootstrap/main.tf
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "terraform_state" {
bucket = "my-devops-state"
# Optionally enable versioning for state rollback
versioning {
enabled = true
}
}
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
Run terraform init and terraform apply in that directory to create the resources. Now you have a backend waiting.
Migrate your main configuration
Back in your main project directory, you might have something like this:
# main.tf (before)
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
Assume this has been applied, so you have a local terraform.tfstate file.
Now add the backend configuration:
# main.tf (after adding backend)
terraform {
backend "s3" {
bucket = "my-devops-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
Run init and migrate
Run terraform init. You'll see output similar to:
Initializing the backend...
Do you want to copy existing state to the new backend?
Pre-existing state was found while migrating the previous "local" backend to the newly configured "s3" backend.
Enter a value:
Type yes and press Enter. Terraform copies the state and confirms:
Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws...
...
Terraform has been successfully initialized!
Now run terraform plan to verify. You should see no changes (or only expected ones). Also check that your local terraform.tfstate is now just a reference file (it will contain a pointer to the remote state, not the full state data).
Verify remote state
To confirm the state is remote, you can look at the .terraform/terraform.tfstate file or use:
terraform state list
If you see your resources listed, you're good. You can also check the S3 bucket console — you'll see prod/terraform.tfstate appear alongside the DynamoDB locks table.
Compare options / when to choose what
When migrating to remote state, you have several backend options. Each has its own pros and cons. Here's a comparison of the most common ones:
| Backend | Locking? | Versioning? | Best for | Considerations |
|---|---|---|---|---|
| S3 (AWS) | Yes (with DynamoDB) | Yes (S3 versioning) | AWS-centric teams | Requires configuring DynamoDB; costs for storage & requests |
| Azure Storage (azurerm) | Yes (via blob lease) | Yes (snapshots) | Azure-centric teams | Requires storage account & container |
| Google Cloud Storage (gcs) | Yes (via bucket lock) | Yes (object versioning) | GCP-centric teams | Requires bucket and service account |
| HashiCorp Cloud (cloud) | Yes (built-in) | Yes | Teams using HCP or Terraform Cloud | Managed service, cost per user |
| local | No | Manual (file copies) | Solo projects, quick experiments | Not safe for team use |
Pro tip: If you're already on AWS, S3 + DynamoDB is the most battle-tested choice. For multi-cloud or managed needs, consider HashiCorp Cloud. Always enable versioning on your state storage for rollback capability.
When to choose what
- Choose S3 if your infrastructure is on AWS and you want tight integration with IAM policies.
- Choose Azure Storage if you're all-in on Azure — maybe your state should live near your resources.
- Choose GCS for GCP projects.
- Choose HashiCorp Cloud if you want zero-ops state management and don't want to maintain the backend yourself. It also gives you a nice UI and policy tools.
Troubleshooting & edge cases
Migrating state can hit snags. Here are common issues and how to fix them:
1. Permission denied when accessing the backend
If terraform init fails with an access error, check your AWS credentials. The error might say AccessDenied or InvalidAccessKeyId. Ensure your IAM user/role has s3:PutObject and s3:GetObject on the bucket, plus dynamodb:PutItem and dynamodb:GetItem on the locks table.
2. Lock acquisition failure: Error acquiring the state lock
This happens when someone else is running Terraform concurrently. If the lock is stale, you can force remove it with terraform force-unlock <lock-id> (the lock ID is usually printed in the error message). Use with caution — only do this if you're sure no other process is actually running.
3. terraform init fails with a state migration prompt but you already have remote state
If you see a prompt to copy state when you didn't expect it, it could be that Terraform doesn't recognize your remote state as existing (e.g., wrong bucket/key). Double-check your backend config values. If you're migrating from S3 to S3 (e.g., different bucket), you might need to use terraform state push — but that's rare.
4. The state file is missing after migration
If you saved a local copy before migrating and then deleted it, you might lose the pointer to remote state. To recover, run terraform init again with the same backend, and it should pull the state from remote. If the remote state is empty, you may have data loss — that's why versioning on your bucket is crucial.
5. Partial migration / state file corrupt
If terraform init fails mid-copy, you could end up with a partial remote state. In that case, check the S3 bucket for the file. You can manually re-initiate migration by deleting the remote state file (if versioning is enabled) and re-running terraform init — but only if you have the original local state.
Pro tip: Always keep a backup of your local
terraform.tfstatebefore migration. Even after successful migration, you can restore it if something goes wrong. Store it in a safe place, like a git repo history (but don't commit secrets!).
What you learned & what's next
In this lesson, you learned the core idea behind migrating from local to remote state — why it's essential for team collaboration, safety, and auditability. You now know the mental model of state as a shared source of truth, and you can execute the migration step by step using a hands-on exercise with AWS S3 and DynamoDB. You also understand how to compare backend options and troubleshoot common migration issues.
You applied this in a practical exercise, which aligns with the learning objectives: you can explain the core concept and complete the migration safely.
Next in the Terraform foundations track, you'll explore workspaces — a way to manage multiple environments (dev, staging, prod) with the same configuration and still keep state files separate. With remote state in place, workspaces become even more powerful because each environment's state lives in its own path in the same backend. That's a perfect follow-up to what you learned today.
Keep going — you're building a solid foundation for production-grade Terraform!
Practice recap
To solidify your skills, try migrating a state from a local directory to a remote S3 backend using a test bucket. Create a simple resource (like a security group), apply it locally, then add the backend block and run terraform init. Verify with terraform plan that there are no unexpected changes. Then, experiment by making a change and running terraform apply from a second terminal to confirm state locking works.
Common mistakes
- Manually copying or editing
terraform.tfstate— always letterraform inithandle the migration; manual edits cause corruption and drift. - Forgetting to set up state locking (e.g., DynamoDB for S3) — without it, concurrent applies can corrupt state even with remote storage.
- Not enabling versioning on the state bucket — losing the ability to roll back if a bad
terraform applyoverwrites state. - Running
terraform initnon-interactively without-input=false— you might miss the copy prompt and end up with no state in the backend.
Variations
- Instead of S3, you can use Azure Storage with
backend "azurerm"and set up a blob container with a lease lock. - For a fully managed experience, switch to HashiCorp Cloud (Terraform Cloud) and use the
cloudblock to store state automatically. - If you're using GCP,
backend "gcs"with a GCS bucket and a lock viagenerationnumbers is a solid option.
Real-world use cases
- A startup team of three developers uses an S3 bucket with DynamoDB locking to safely share state for their AWS-based microservices.
- A DevOps team migrates legacy local-state infrastructure to Terraform Cloud to enable remote operations and policy checks across multiple regions.
- An enterprise migrates from local to Azure Blob Storage backend to align with company cloud strategy and use snapshots for state rollback.
Key takeaways
- Local state prevents collaboration and risks data loss — remote state is essential for team workflows.
- Remote state stores your
terraform.tfstatein a shared backend like S3 with locking via DynamoDB. - Migrate safely by preparing the backend, updating the
backendblock, and lettingterraform initcopy the state. - Always enable versioning and locking on your backend to protect against corruption and concurrent writes.
- Troubleshoot migration issues by checking permissions, lock IDs, and ensuring the backend configuration is correct.
- Workspaces are the next step — they let you manage multiple environments with the same config using remote state per environment.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.