Terraform

Understanding Terraform State: What It Is and Why It Matters

AGAnurag Gupta04 Apr 2025 Β· Updated 04 Oct 2026 Β· 7 min read
Understanding Terraform State: What It Is and Why It Matters

Quick answer: Terraform state is a JSON document (terraform.tfstate) that records which real-world resources Terraform created and what their attributes were at the last apply. Terraform compares your .tf code against this state, and the state against the real cloud, to work out the minimum set of changes to make. Lose or corrupt the state and Terraform forgets what it owns; that is why state must be stored remotely, locked, encrypted and never edited by hand.

Almost every scary Terraform story β€” the accidental recreate, the “resource already exists” error, the two engineers who overwrote each other’s changes β€” is a state story. This guide explains what the state file contains, how Terraform uses it during plan and apply, local versus remote storage, the terraform state commands you will actually use, and the habits that keep state safe.

What the state file is and why Terraform needs it

Your .tf files describe the desired infrastructure. The cloud API holds the actual infrastructure. State is the bridge between them: a mapping from each resource address in your code (aws_instance.web) to the real object’s ID (i-0abc123) and its last-known attributes.

Without that mapping Terraform could not answer basic questions. Which of the 300 EC2 instances in this account is “my” aws_instance.web? Did I create this security group, or did someone else? Should changing instance_type update the existing machine or build a new one? State answers all of these.

State also stores metadata that is not available from the API: resource dependencies (so Terraform destroys things in the right order), provider configuration for each resource, and output values. For the basics of how resources are declared before they ever reach state, see Creating and Managing Infrastructure Resources in Terraform.

Inside a state file

You should never edit state manually, but reading it is instructive. A trimmed example after creating one S3 bucket:

{
  "version": 4,
  "terraform_version": "1.9.5",
  "serial": 7,
  "lineage": "3f2b9c1e-6a7d-4f1a-9c0b-5e8d2a1b7c44",
  "outputs": {
    "bucket_arn": {
      "value": "arn:aws:s3:::tkh-demo-logs",
      "type": "string"
    }
  },
  "resources": [
    {
      "mode": "managed",
      "type": "aws_s3_bucket",
      "name": "logs",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "arn": "arn:aws:s3:::tkh-demo-logs",
            "bucket": "tkh-demo-logs",
            "id": "tkh-demo-logs",
            "region": "ap-south-1",
            "tags": { "ManagedBy": "terraform" }
          },
          "dependencies": []
        }
      ]
    }
  ]
}

Key fields:

  • serial increments on every write; backends use it to detect stale writes.
  • lineage is a unique ID for this state’s history. Terraform refuses to overwrite state with a different lineage, which protects you from pointing two projects at one file.
  • mode is managed for resources and data for data sources.
  • attributes holds every value the provider returned, including sensitive ones such as database passwords, in plain text.

How Terraform uses state during plan and apply

Every terraform plan runs the same loop:

  1. Read state from the configured backend.
  2. Refresh: for each resource in state, ask the provider for its current real attributes and update the in-memory copy. This is how Terraform notices that someone changed a tag in the console.
  3. Diff: compare the refreshed state with the desired configuration and compute create, update, replace or destroy actions.
  4. On apply, execute those actions and write the new state back, bumping serial.

This is why a resource deleted by hand in the console shows up in the next plan as “will be created”: state said it existed, refresh found it gone, configuration still wants it. The ordering of those actions comes from the dependency graph, which Terraform builds partly from state; Understanding Resource Dependencies and Ordering in Terraform covers that in depth.

Local vs. remote state

Local state (default) Remote state (backend)
Where it lives terraform.tfstate in your working directory S3, Azure Blob, GCS, HCP Terraform, Consul, PostgreSQL and others
Team access One laptop only Everyone and CI share one source of truth
Locking None across machines Supported by most backends (DynamoDB or native S3 locking, blob leases, HCP)
Encryption and backup Whatever your disk has Server-side encryption, versioning, access logs
Good for Learning and throwaway experiments Anything shared or long-lived

Switching is a few lines. Here is a production-grade S3 backend for an AWS project in Mumbai:

terraform {
  backend "s3" {
    bucket       = "tkh-terraform-state"
    key          = "payments/prod/terraform.tfstate"
    region       = "ap-south-1"
    encrypt      = true
    use_lockfile = true   # native S3 locking, Terraform 1.10+
  }
}

Run terraform init after adding the block and Terraform offers to migrate the existing local state into the bucket. Enable bucket versioning so every previous state is recoverable.

The terraform state commands you will actually use

Terraform ships a family of subcommands for inspecting and surgically adjusting state. They are safer than a text editor because they validate the result and (with remote backends) take a lock.

# See everything Terraform manages
terraform state list

# Inspect one resource's recorded attributes
terraform state show aws_s3_bucket.logs

# Rename a resource in state after refactoring code (no destroy/create)
terraform state mv aws_instance.web aws_instance.app

# Stop managing a resource without deleting it in the cloud
terraform state rm aws_iam_user.legacy

# Bring an existing resource under management
terraform import aws_s3_bucket.assets tkh-prod-assets

# Download a copy of the current state as JSON
terraform state pull > backup.tfstate

Since Terraform 1.1 you can replace most state mv calls with a declarative moved block in code, and since 1.7 a removed block replaces state rm. Both are reviewable in a pull request, which ad-hoc CLI commands are not. For bringing resources in, Terraform 1.5+ also offers import blocks that generate configuration for you.

Six state mistakes that cause outages

  1. Committing terraform.tfstate to Git. State contains secrets in plain text and merge conflicts corrupt it. Add *.tfstate* to .gitignore and use a remote backend.
  2. Editing the JSON by hand. A typo in serial or a missing comma makes the file unreadable. Use terraform state commands or moved and removed blocks.
  3. Two root modules sharing one state key. Each applies will try to destroy the other’s resources. One backend key per root module, always.
  4. Running apply without a lock. Two concurrent applies both write state; the slower one wins and the faster one’s resources are orphaned. Enable locking on your backend.
  5. Treating state as the only truth. Drift happens. Run terraform plan regularly in CI and alert on unexpected changes.
  6. No backups or versioning. Turn on S3 bucket versioning or the equivalent, so a bad write can be rolled back with terraform state push.

Frequently asked questions

Does the state file contain secrets?

Yes. Database passwords, private keys and tokens returned by providers are stored in plain text, even when marked sensitive (that only hides them in CLI output). Encrypt the backend and restrict read access as tightly as you would for the secrets themselves.

What is the difference between terraform refresh and plan?

plan refreshes state in memory and shows the diff without saving. The standalone refresh command writes the refreshed state immediately and is deprecated; use terraform apply -refresh-only when you want to accept drift into state.

Can I recover if the state file is deleted?

If you have backend versioning, restore the previous object. If not, you must re-import every resource with terraform import. This is slow and error-prone, which is why versioning is non-negotiable.

What is a state backup file?

With local state, every write produces terraform.tfstate.backup containing the previous version. Remote backends do not create this file; they rely on the storage service’s own versioning.

Key takeaways

  • State maps your code’s resource addresses to real cloud objects; Terraform cannot plan without it.
  • It is JSON, it contains secrets, and it must be stored remotely, encrypted, versioned and locked.
  • Use terraform state list / show / mv / rm, or moved, removed and import blocks, instead of editing the file.
  • One state per root module, backups turned on, and regular plans to catch drift.

Ready to run Terraform the way production teams do, with remote state, locking and CI pipelines? Our DevOps course covers Terraform end to end with live cloud labs, mentor support and placement assistance. You can also watch the hands-on sessions on our YouTube channel.

AG
Written byAnurag Gupta

Part of the Techknowledgehub team of industry mentors, writing practical guides to help you build a job-ready tech career.

More articles by Anurag Gupta β†’
Keep reading

Related articles

Leave a Reply