Skip to main content

Terraform State — What It Is, Why It Matters, and How to Manage It

Understand Terraform state — what the tfstate file contains, why it is the source of truth for your infrastructure, and how to inspect and manage it safely.

26 Terms

Overview and What You Will Learn

Terraform state is the most important concept to understand after you know how to write HCL. Without it, Terraform would have no way to know what infrastructure it created last time — it would try to create everything from scratch on every apply, hit naming conflicts, and leave your cloud account in chaos.

In this guide you will learn exactly what the state file contains, why it is Terraform's source of truth, how to use state commands to inspect and surgically modify state, and what happens when state gets out of sync with reality.

By the end you will be able to:

  • Explain what the terraform.tfstate file contains and why Terraform needs it
  • Use terraform state list and terraform state show to inspect any resource
  • Move, rename, and remove resources from state without destroying them
  • Understand what drift is and how to detect it
  • Know exactly what to do when state gets corrupted or out of sync

Why This Matters in Production

At Zerodha, a junior engineer was cleaning up their local terraform directory and deleted the terraform.tfstate file thinking it was a build artifact. They ran terraform apply to recreate it — Terraform had no memory of what it had created, so it tried to create everything again. The S3 bucket names conflicted. The security group names conflicted. The apply failed halfway through, leaving the environment in a broken state that took three hours to untangle.

Understanding state — what it is, where it lives, and how to protect it — is the difference between Terraform being a reliable tool and a liability.

Core Principles

State as the Bridge Between Code and Reality
Bash
+------------------------------------------+
| Your .tf files (desired state) |
| resource "aws_instance" "app" { |
| instance_type = "t3.large" |
| } |
+------------------------------------------+
|
v
+------------------------------------------+
| terraform.tfstate (Terraform's memory) |
| { |
| "resource": "aws_instance.app", |
| "id": "i-0a1b2c3d4e5f", |
| "instance_type": "t3.large", |
| "public_ip": "13.126.x.x" |
| } |
+------------------------------------------+
|
v
+------------------------------------------+
| Real AWS Infrastructure |
| EC2 instance i-0a1b2c3d4e5f |
| running in ap-south-1 |
+------------------------------------------+

Terraform uses state to answer three questions on every plan:

  • What resources did I create before? (state file)
  • What does the configuration say they should look like now? (.tf files)
  • What does the cloud say they actually look like right now? (API refresh)

The diff between those three things is what you see in terraform plan.

What Is Actually Inside the State File

The state file is a JSON document. Here is a simplified version of what one resource entry looks like:

JSON
{
"version": 4,
"terraform_version": "1.6.3",
"resources": [
{
"mode": "managed",
"type": "aws_instance",
"name": "web",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 1,
"attributes": {
"id": "i-0a1b2c3d4e5f6789",
"ami": "ami-0f5ee92e2d63afc18",
"instance_type": "t3.large",
"public_ip": "13.126.45.67",
"private_ip": "10.0.1.50",
"tags": {
"Environment": "prod",
"ManagedBy": "terraform",
"Name": "razorpay-api-prod-web-server"
},
"vpc_security_group_ids": [
"sg-0a1b2c3d4e5f"
]
}
}
]
}
]
}

Every attribute of every resource is stored here — IDs, IP addresses, ARNs, tags, and any sensitive values like database passwords. This is why the state file must be protected: it is a complete inventory of your infrastructure, including secrets.

Detailed Step-by-Step Practical Lab

Step 1 — Inspect Your State File

After running terraform apply, explore what Terraform recorded:

Bash
terraform state list
# Example output:
# data.aws_ami.amazon_linux
# data.aws_caller_identity.current
# aws_instance.web
# aws_s3_bucket.app_assets
# aws_s3_bucket_public_access_block.app_assets
# aws_s3_bucket_server_side_encryption_configuration.app_assets
# aws_s3_bucket_versioning.app_assets
# aws_security_group.web
# Show every stored attribute of a specific resource
terraform state show aws_instance.web
# Output will show every attribute Terraform knows about:
# resource "aws_instance" "web" {
# ami = "ami-0f5ee92e2d63afc18"
# arn = "arn:aws:ec2:ap-south-1::instance/i-0a1b2c3d4e5f"
# id = "i-0a1b2c3d4e5f6789"
# instance_type = "t3.large"
# private_ip = "10.0.1.50"
# public_ip = "13.126.45.67"
# tags = {
# "Environment" = "prod"
# "ManagedBy" = "terraform"
# "Name" = "razorpay-api-prod-web-server"
# }
# vpc_security_group_ids = [
# "sg-0a1b2c3d4e5f",
# ]
# ...many more attributes...
# }
Tip

terraform state show is your best debugging tool. When a plan shows a change you did not expect, run terraform state show <resource> to see exactly what Terraform currently believes is in the cloud — then compare it to what is really there.

Step 2 — Move a Resource in State (Rename Without Destroy)

You renamed a resource block in your .tf files — from aws_instance.web to aws_instance.app_server. Without updating state, Terraform will try to destroy the old one and create a new one. Use terraform state mv to rename it in state without touching the real infrastructure:

Bash
# Rename a resource in state — NO real infrastructure changes
terraform state mv aws_instance.web aws_instance.app_server
# Move: aws_instance.web => aws_instance.app_server
# Successfully moved 1 object(s).
# Verify the move
terraform state list | grep instance
# aws_instance.app_server <- renamed in state
# Now update your .tf file to match:
# resource "aws_instance" "app_server" { <- was "web"
# ...
# }
# Confirm plan shows no changes
terraform plan
# No changes. Your infrastructure matches the configuration.
Step 3 — Remove a Resource From State (Without Destroying It)

Sometimes you want to stop managing a resource with Terraform — without deleting it. terraform state rm removes it from state, leaving the real resource untouched:

Bash
# Remove a resource from Terraform state — does NOT delete the real resource
terraform state rm aws_s3_bucket.legacy_logs
# Removed aws_s3_bucket.legacy_logs
# Successfully removed 1 resource instance(s).
# The S3 bucket still exists in AWS — Terraform just no longer tracks it
# Verify:
terraform state list | grep legacy
# (nothing — it is gone from state but still in AWS)

Use cases for terraform state rm:

  • Moving a resource to a different Terraform configuration or module
  • Handing off a resource to another team's configuration
  • Stopping management of a resource without deleting it
Step 4 — Pull and Push State Manually

When using a remote backend, you can pull the current state to inspect it locally and push a modified state back:

Bash
# Pull the current remote state to stdout
terraform state pull
# Pull and save to a local file for inspection
terraform state pull > state-backup-$(date +%Y%m%d).json
# Push a modified state file back to the remote backend
# Only do this when explicitly fixing state corruption — dangerous operation
terraform state push state-backup-20240115.json
Security

Never edit the state file by hand with a text editor. The state file contains checksums and internal references. Manual editing almost always corrupts it. Use terraform state mv, terraform state rm, and terraform import instead — these are the safe surgery tools.

Step 5 — Understand What Happens When State is Lost

If the state file is deleted or becomes unavailable, Terraform loses all memory of what it created. The next terraform plan will show every resource as needing to be created fresh — because from Terraform's perspective, nothing exists.

Bash
# Simulate state loss (do NOT do this in production)
mv terraform.tfstate terraform.tfstate.deleted
terraform plan
# aws_instance.web will be created <- Terraform thinks it does not exist
# aws_s3_bucket.app_assets will be created <- Terraform thinks it does not exist
# Plan: 6 to add, 0 to change, 0 to destroy.
# The real resources still exist in AWS — Terraform just forgot about them
# Recovery: import every resource back into state
terraform import aws_instance.web i-0a1b2c3d4e5f6789
terraform import aws_s3_bucket.app_assets razorpay-api-dev-assets-123456789012
# Repeat for every resource...

This is why remote state with versioning is non-negotiable for any team. With S3 versioning enabled, any previous version of the state file is recoverable from the S3 console.

Step 6 — Detect and Understand Drift

Drift is what happens when someone changes infrastructure outside of Terraform. Every terraform plan detects it automatically:

Bash
# Scenario: someone increased the instance type in the AWS console
terraform plan
# ~ aws_instance.web will be updated in-place
# ~ instance_type = "t3.large" -> "t3.medium"
# <- Terraform will REVERT this to match your .tf file
#
# This "change" is Terraform noticing the drift and planning to fix it
# Three options:
# 1. Apply normally — Terraform reverts the console change (enforces IaC)
# 2. Update your .tf file to accept the new instance type (adopt the change)
# 3. Apply -refresh-only — update state to record the change without reverting
terraform apply -refresh-only

Production Best Practices and Common Pitfalls

  • Never edit the state file by hand. Use terraform state mv, terraform state rm, terraform import, and terraform force-unlock. These commands are safe — manual edits are not.

  • Always use remote state for teams. Local state is only acceptable for solo learning projects. The moment a second engineer exists, use S3 + DynamoDB. Set it up before the second engineer joins — migrating state later is extra work.

  • Enable S3 versioning on your state bucket. A versioned state bucket means any corrupt or accidentally modified state file is recoverable. Without versioning, a corrupted state file may be unrecoverable.

  • Restrict access to the state file. The state file contains plaintext sensitive values. Only the Terraform apply role and trusted administrators should have s3:GetObject on the state bucket. Developers who only need to read plan output do not need direct state access.

  • Add prevent_destroy to your state bucket. The state S3 bucket is the most critical resource in your infrastructure setup. A lifecycle rule with prevent_destroy = true prevents it from being accidentally deleted by a Terraform run.

  • Back up state before major operations. Before running terraform state mv, terraform state rm, or any large refactor, pull the current state and save it: terraform state pull > state-backup-$(date +%Y%m%d).json.

Quick Reference and Troubleshooting Commands

Command What It Does
terraform state list List all resources tracked in state
terraform state list module.vpc List resources within a specific module
terraform state show <addr> Show all stored attributes of one resource
terraform state mv <src> <dst> Rename or move a resource in state — no infrastructure change
terraform state rm <addr> Remove a resource from state — does NOT destroy real resource
terraform state pull Download remote state to stdout
terraform state push <file> Upload a local state file to the remote backend
terraform plan -refresh-only Preview what drift exists without making changes
terraform apply -refresh-only Update state to match reality — no infrastructure changes
terraform force-unlock <id> Break a stuck state lock — confirm no apply is running first
Error Root Cause Fix
No state file found State file deleted or wrong directory Check directory, restore from backup or re-import resources
Error: state snapshot was created by Terraform v1.x State version mismatch Upgrade Terraform CLI to match the version that wrote the state
Resource already exists in state Trying to import a resource that is already tracked Check terraform state list first
Error: Cycle Circular dependency between resources Remove circular reference — use depends_on instead of direct references
Plan shows all resources as new State file is empty or lost Re-import resources or restore state from S3 versioned backup
Error acquiring the state lock Another apply is running Wait for it to complete, or terraform force-unlock if it is stuck

Resources

Explore More in Terraform State and Backend Management

All 3 Topics

Frequently Asked Questions

Is Terraform State — What It Is, Why It Matters, and How to Manage It free to learn on DevOps Network?

Yes - this topic, like everything on DevOps Network, is 100% free with no paywall or sign-up gate.

What does the Terraform State — What It Is, Why It Matters, and How to Manage It topic cover?

Understand Terraform state — what the tfstate file contains, why it is the source of truth for your infrastructure, and how to inspect and manage it safely.