Terraform State and Backends

TerraformDevOpsInfrastructureSecurity
Share on LinkedIn Share on X Share on Reddit Share on HN Share on Bluesky

Terraform state is a JSON file that knows your RDS instance is db-ABC123 and that the security group depends on the VPC. Lose it, corrupt it, or let two applies run concurrently, and you are in for an afternoon of terraform import and prayer. Remote backends with locking are table stakes; the harder problems are partitioning, secrets in state, and refactoring resources without destroy-and-recreate.

Local vs remote state

Local terraform.tfstate on a laptop works for solo experiments. It fails for teams:

Remote backend minimum:

terraform {
  backend "s3" {
    bucket         = "org-terraform-state"
    key            = "prod/network/terraform.tfstate"
    region         = "eu-west-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

Enable S3 versioning on the bucket — previous state versions are your undo button.

Locking mechanics

DynamoDB lock item prevents parallel apply/plan-with-lock. Stuck lock after CI crash:

terraform force-unlock LOCK_ID

Only after confirming no running apply. Force-unlock during active apply causes state corruption.

State partitioning strategy

Split by Example key path
Environment prod/eks/terraform.tfstate
Layer prod/network/, prod/apps/
Team platform/vpc/, data/rds/

Anti-pattern: one state for entire prod account — 45-minute plans, everyone blocked.

Cross-stack references:

data "terraform_remote_state" "network" {
  backend = "s3"
  config = {
    bucket = "org-terraform-state"
    key    = "prod/network/terraform.tfstate"
    region = "eu-west-1"
  }
}

subnet_ids = data.terraform_remote_state.network.outputs.private_subnet_ids

Terraform 1.5+ terraform stack and Cloud stacks evolve this pattern — same principle: explicit outputs, no duplicate resources.

Sensitive values in state

State contains resource attributes, including secrets marked sensitive in plan output. Treat state bucket as highly confidential:

Prefer referencing Secrets Manager/SSM rather than storing secret strings in Terraform resources when possible. If password must flow through Terraform, rotate after apply.

Import and adoption

Bring existing infrastructure under management:

terraform import aws_instance.web i-0abc123def456

Write matching .tf first, then import. Plan should show no changes after successful import.

Bulk adoption: tools like terraformer generate rough .tf from scan — always refactor into modules afterward.

Refactoring without destroy — moved and import blocks

Terraform 1.1+ moved block retargets state when renaming resources:

moved {
  from = aws_security_group.old
  to   = aws_security_group.web
}

import block (1.5+) declarative import in config:

import {
  to = aws_instance.web
  id = "i-0abc123"
}

Avoid manual terraform state mv when declarative alternatives exist — they are reviewable in PR.

Recovery playbook

Corrupted state: restore previous S3 version.

Accidental destroy: restore state version; re-apply if resources still exist, or recreate from module.

Resource drifted outside state: import or remove from config.

Splitting state: terraform state pull, carefully edit JSON (expert only), or use terraform state mv -state-out to new backend.

Always backup before manual state surgery.

CI integration

State access control and auditing

Limit who can read prod state — it contains secrets and infrastructure topology useful to attackers. Use IAM policies on S3 bucket and DynamoDB lock table. Enable CloudTrail data events on state bucket. Terraform Cloud adds RBAC and audit log of who ran apply — worth it for regulated industries even if S3 backend suffices technically.

Workspace vs directory layout

Pattern State files Use case
Monorepo workspaces One per env/stack Platform team, shared modules
Directory per env env/prod, env/staging Simple prod/staging split
Terragrunt Generated backends DRY backend config
# environments/prod/backend.tf
terraform {
  backend "s3" {
    bucket         = "tf-state-prod"
    key            = "network/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "tf-locks-prod"
    encrypt        = true
  }
}

Never share state files between environments — staging terraform destroy must not touch production resources.

State size management

Large states (>10 MB) slow every plan:

Monitor terraform plan duration — sudden 10× increase often means someone imported thousands of resources into one state file.

Pair with Terraform modules composition when splitting state along module boundaries.

State locking and team workflows

Remote state without locking corrupts on concurrent apply. S3 backend uses DynamoDB for locks; GCS uses native locking; Terraform Cloud provides managed locking. Never disable locking to "speed up" CI — two applies racing produce state corruption that takes hours to untangle. Use separate state files per environment and per blast-radius boundary — one monolithic state for entire org means every apply risks everything.

Encryption and access logging

S3 state buckets require SSE-KMS or SSE-S3, versioning enabled, and public access blocked at account level. CloudTrail data events on state bucket object reads catch unauthorized access. Restrict s3:GetObject on state to CI role and break-glass admin role only — developer laptops do not need direct state download.

Resources

terraform state management backends rollout

Field RUM on Android 4G. Rollback documented in the PR. Test back navigation and offline recovery.

terraform state management backends rollout

Field RUM on Android 4G. Rollback documented in the PR. Test back navigation and offline recovery.

terraform state management backends rollout

Field RUM on Android 4G. Rollback documented in the PR. Test back navigation and offline recovery.

terraform state management backends rollout

Field RUM on Android 4G. Rollback documented in the PR. Test back navigation and offline recovery.

terraform state management backends rollout

Field RUM on Android 4G. Rollback documented in the PR. Test back navigation and offline recovery.

State is a database of record

Encrypt and version the backend; require locks on every apply. Split state by blast radius. CI plans broadly and applies narrowly. Never share prod and non-prod state.

Practice restoring a prior state version and planning against it. Treat backend migrations like datastore migrations. Partial applies leave orphans — your runbook should know how to find them in the cloud console.

Verification layer 1 for terraform state management backends

Define an acceptance check for layer 1: failure injection, timeout behavior, and rollback. Keep it next to the code that implements terraform state management backends. Reviewers confirm the check fails when the control is disabled.

Verification layer 2 for terraform state management backends

Define an acceptance check for layer 2: failure injection, timeout behavior, and rollback. Keep it next to the code that implements terraform state management backends. Reviewers confirm the check fails when the control is disabled.

Verification layer 3 for terraform state management backends

Define an acceptance check for layer 3: failure injection, timeout behavior, and rollback. Keep it next to the code that implements terraform state management backends. Reviewers confirm the check fails when the control is disabled.

Verification layer 4 for terraform state management backends

Define an acceptance check for layer 4: failure injection, timeout behavior, and rollback. Keep it next to the code that implements terraform state management backends. Reviewers confirm the check fails when the control is disabled.

Frequently asked questions

Why does Terraform need state?

Terraform state maps resource identifiers in configuration to real cloud resource IDs, tracks metadata for dependency ordering, and records attributes needed for other resources. Without state, Terraform cannot know whether to create or update an existing resource on apply. State is the source of truth for Terraform's view of the world — distinct from but synchronized with actual infrastructure.

What is the recommended remote backend for Terraform?

AWS S3 with DynamoDB table for state locking is the most common open-source pattern: durable, versioned state files and mutual exclusion during apply. Terraform Cloud and HCP Terraform provide managed remote state with RBAC and run history. GCS and Azure Blob with native locking are equivalents on other clouds.

How should you partition Terraform state?

Split by blast radius and team ownership — separate state per environment (dev/staging/prod) and per domain (network, compute, data). Smaller states plan faster and limit damage from bad apply. Use terraform_remote_state data source or stack outputs (Terraform 1.5+ stacks) to pass references between partitions — never duplicate resource management across states.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →