Terraform State Management: Backends, Locking, and Drift

Philip Rehberger Nov 2, 2026 8 min read

Stop fighting state. Remote backends, workspace strategy, and drift remediation.

Terraform state is the source of truth for your infrastructure. The state file describes what Terraform thinks is real — every resource, its attributes, its dependencies. Mess this up and you can corrupt or accidentally destroy production. Most Terraform problems trace back to state management, either too little or too much.

This post is the patterns that hold up: where to put state, how to lock it, how to handle drift, and the operations that the documentation does not emphasize.

What State Is For

State serves three roles:

The mapping. Terraform configurations describe what you want; state describes what exists. Terraform compares the two to plan changes.

The cache. State caches attribute values for fast plans. Without it, every plan would have to query every resource.

The contract. State records which Terraform run created which resources. This makes "remove this resource" safe — Terraform knows it owns the resource.

State is not optional. Even a single-developer "terraform apply" creates a state file. The question is where it lives and how it is managed.

Backend Options

State backends are where Terraform stores the state file.

Local. A file on the running machine. Default for terraform init without configuration. Wrong for any real use — state lives only on one developer's laptop.

S3 + DynamoDB. Industry default for AWS. State in S3, locking via DynamoDB. Works well, requires explicit setup.

terraform {
  backend "s3" {
    bucket         = "my-tfstate"
    key            = "prod/network.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

GCS (Google Cloud Storage). Similar pattern for GCP. Locking is built into GCS now (object versioning); no separate locking table needed.

Azure Storage. Equivalent for Azure.

HashiCorp Terraform Cloud / Terraform Enterprise. Hosted state with locking, secrets management, runs, policy. The HashiCorp commercial offering.

Terraform alternatives (Spacelift, env0, etc.): Commercial state management with various features.

For most teams: S3+DynamoDB on AWS, GCS on GCP, Azure Storage on Azure. Terraform Cloud is the right call for organizations that want the SaaS experience and can pay for it.

State Locking

Two engineers running terraform apply simultaneously is a recipe for corrupt state. Locking prevents this.

DynamoDB-based locking: Terraform acquires a lock row before running, releases after. A second concurrent run fails with "lock could not be acquired."

backend "s3" {
  # ...
  dynamodb_table = "terraform-locks"
}

The DynamoDB table needs a single LockID key. The table is cheap (a few writes per Terraform run).

GCS now has native locking. Azure Storage uses blob leases.

Without locking, simultaneous runs produce undefined behavior. Always configure it.

Splitting State

The biggest state-related design decision: how much do you put in one state file?

A single state file for all infrastructure is convenient until it is not:

  • Plans take forever (Terraform has to refresh every resource)
  • One bad change can affect any resource
  • One person locking the state blocks all other Terraform work
  • The blast radius of "terraform destroy" is everything

The pattern that scales: split state by lifecycle or ownership.

states/
├── network/         # VPCs, subnets, routing — rarely changes
├── shared-services/ # DNS, certificate manager — semi-stable
├── eks-cluster/     # The cluster itself
├── workloads/
│   ├── auth/        # Auth service infrastructure
│   ├── payments/    # Payments service infrastructure
│   └── frontend/    # Frontend infrastructure
└── data/            # Databases, caches

Each subdirectory has its own backend configuration with a different state file. Workloads can be deployed independently. Network changes do not require thinking about the auth service.

The trade: cross-state references use terraform_remote_state data sources, which add coupling. Splitting state means thinking carefully about boundaries.

Workspaces

Terraform has a workspace concept that creates multiple states from the same configuration.

terraform workspace new staging
terraform workspace new production

Each workspace has its own state. Same code, different environments.

Workspaces are useful for trivial differences. They become a problem when environments diverge:

  • Variables are shared (the same production.tfvars for all workspaces)
  • Backend configuration is shared
  • Cross-environment references are hard

Most teams that use Workspaces start there and migrate to directory-based separation as environments diverge. For greenfield: directory-based from the start.

Importing Existing Resources

Resources that exist but are not in state need to be imported.

# Old syntax (still works)
terraform import aws_instance.web i-0abc123def

# New syntax (Terraform 1.5+) — declarative
import {
  to = aws_instance.web
  id = "i-0abc123def"
}

The declarative import block lets you commit imports as code. They are processed on the next terraform plan.

For a real "we have a lot of unmanaged resources" migration:

  • Tools like terraformer or aws2tf can generate Terraform code + imports from existing resources.
  • The generated code is rarely production-ready; expect significant cleanup.
  • Import gradually, in batches that fit one PR each.

This is the slowest part of adopting Terraform on existing infrastructure. Plan for it taking months.

Drift Detection

State drifts from reality. Someone clicks in the console, an autoscaler adjusts something, an operator changes a value. Terraform's plan shows the drift.

terraform plan
# Shows differences between state and real infrastructure

For systematic drift detection: scheduled plan runs that alert on differences. Terraform Cloud, Spacelift, and similar tools do this; you can also build it with cron and notifications.

Drift detection is most valuable when the consequences are clear: drift is investigated, either by reconciling state or fixing the configuration. Drift that produces "alerts nobody reads" is worse than no drift detection.

State Surgery

Sometimes you have to manipulate state directly. The common operations:

Move a resource within state:

terraform state mv aws_instance.old aws_instance.new

Used when renaming a resource in code; the underlying instance is the same.

Remove a resource from state without destroying:

terraform state rm aws_instance.web

Used when the resource should no longer be managed by Terraform but should not be destroyed.

Import a resource that already exists:

Covered above.

Refresh state from reality:

terraform apply -refresh-only

Pulls current attributes for all resources without changing anything.

These operations are dangerous. Backup state first. Most should be reviewed by someone other than the person running them.

State Backup

The state file is critical. Lose it and Terraform has no idea what it manages.

S3 backend with versioning enabled keeps every revision of state. A bad state push can be rolled back.

resource "aws_s3_bucket_versioning" "state" {
  bucket = aws_s3_bucket.state.id
  versioning_configuration {
    status = "Enabled"
  }
}

For additional safety: periodic state exports to a separate bucket or region. Belt and suspenders.

State Secrets

Terraform state contains sensitive data — database passwords, API keys, anything Terraform creates or reads. The state file is a sensitive document.

Mitigations:

  • Encrypt state at rest (S3 SSE, GCS encryption).
  • Restrict who can read the state bucket. Most engineers do not need direct state access.
  • Avoid embedding secrets in Terraform variables. Use Vault, Secrets Manager, etc., and reference them at runtime.

Treat state files like credentials. They contain credentials.

Refactoring State

As Terraform code is refactored — modules created, resources reorganized — state has to follow. The moved block makes this safer:

moved {
  from = aws_instance.web
  to   = module.web_servers.aws_instance.this
}

Terraform recognizes the move and updates state without destroying and recreating the resource.

Before the moved block (introduced in 1.1), refactoring required manual terraform state mv commands. The new syntax is dramatically safer.

Common Mistakes

  • Local state in production. Loses state when the developer's laptop dies.
  • No state locking. Concurrent runs corrupt state.
  • One giant state file. Slow plans, large blast radius.
  • Workspaces for fundamentally different environments. Causes pain as environments diverge.
  • No state versioning. No recovery from bad state pushes.
  • Plain-text secrets in state. State files leak.
  • Manual state surgery without backup. State corruption that cannot be recovered.

A Practical Setup

For a typical AWS team:

  1. S3 bucket for state with versioning, encryption, and restricted access.
  2. DynamoDB table for locking.
  3. Separate state files per logical component (network, shared, per-service).
  4. Directory-based environments (dev/, staging/, prod/), not workspaces.
  5. Scheduled drift detection with alerts.
  6. CI runs Terraform with explicit credentials, not developer credentials.

This setup handles the typical needs. Beyond this: Terraform Cloud or commercial alternatives for teams that want the managed experience.

The Real Lesson

State is where the action is in Terraform. Configuration is the user-facing artifact, but state is what Terraform actually operates on. Teams that handle state well operate confidently. Teams that do not eventually have a state-corruption incident that costs days.

Set up state correctly once. The investment pays back every time you do not have a state-related incident.


Designing Terraform state management for a new team, or untangling state issues in an existing setup? We help teams structure state so the blast radius of changes is bounded and the operational story is clear. scopeforged.com

Share this article

Related Articles

Need help with your project?

Let's discuss how we can help you build reliable software.