Terraform State: What It Is and Why You Should Fear Losing It

The Uncomfortable Truth About State

Terraform doesn’t query your cloud every time to figure out what exists. It trusts a file.

That file is terraform.tfstate. It’s a JSON document that maps every resource in your config to the real-world object it created. It’s Terraform’s memory — the bridge between the infrastructure you described in HCL and the actual resources running in your cloud account.

Lose it, and Terraform has amnesia. Delete terraform.tfstate and run terraform plan. Terraform thinks nothing exists. It will try to create everything from scratch — even if those resources are already running in production. You’ll get conflicts, duplicates, or a terrifying plan that wants to rebuild your entire environment.

State is not a backup. It’s not optional. It’s not a nice-to-have. It’s the single source of truth that makes the entire Terraform workflow possible. Every plan, every apply, every destroy — they all start by reading the state file. Without it, Terraform is blind.

What State Actually Contains

The state file is JSON. Understanding its structure helps you reason about what Terraform knows and what it does with that knowledge.

{
  "version": 4,
  "terraform_version": "1.12.0",
  "resources": [
    {
      "mode": "managed",
      "type": "aws_instance",
      "name": "web",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "id": "i-0abc123def456",
            "ami": "ami-0c55b159cbfafe1f0",
            "instance_type": "t2.micro",
            "public_ip": "54.123.45.67",
            "private_ip": "10.0.1.42",
            "tags": { "Name": "web-server" }
          }
        }
      ]
    }
  ]
}

Key things stored in state:
– Resource type and name — the logical identity in your config (aws_instance.web)
– Provider — which provider manages this resource
– Real-world attributes — every property of the actual cloud resource: IDs, IPs, ARNs, DNS names, tags
– Dependencies — which other resources this one depends on (used for destroy ordering)

Here’s the important implication: state contains sensitive data. Database passwords, API keys, private IP addresses, ARNs — everything the cloud API returns about a resource ends up in the state file. If you create an RDS instance with a password, that password is stored in plaintext in the state file. This is why secure state storage isn’t optional for production workloads. It’s why you never commit state to Git.

The Three-Way Relationship — How Terraform Thinks

Terraform operates across three things at all times:

Your Config (.tf files)  ←→  Terraform State (.tfstate)  ←→  Real Infrastructure

Every plan and apply follows this logic:
1. Read your .tf files → this is the desired state (what you want)
2. Read the state file → this is the known state (what Terraform thinks exists)
3. Optionally refresh by querying the cloud API → this is the actual state (what really exists)
4. Compute the diff between desired and known/actual → this is the plan

The state is the middle layer. It’s the bridge between what you want and what actually exists. Without it, Terraform would have to query every possible resource in your cloud account on every plan to figure out what it manages — which is both slow and impractical (how would it know which of the 200 EC2 instances in your account belongs to this config?).

This is a point people miss: state isn’t just a performance optimization. It’s the mechanism that gives Terraform identity. The state file says “resource aws_instance.web in your config corresponds to real-world instance i-0abc123def456.” Without that mapping, Terraform has no way to know which cloud resource each config block refers to.

What State Tracks (And Why Each Piece Matters)

Resource IDs — The Identity Mapping
When you create an aws_instance, AWS assigns it an ID (i-0abc123...). Terraform stores this ID in state. On the next apply, it uses this ID to find and update the right resource — not just any instance that happens to match your config. This is how Terraform distinguishes between “update the existing server” and “create a new server.”

Metadata and Dependencies — Ordering Operations
State tracks which resources depend on each other. This is how Terraform determines the correct sequence for creation and destruction. Create the VPC before the subnet. Create the subnet before the EC2 instance. Destroy in reverse order: instance first, then subnet, then VPC. Without dependency tracking in state, Terraform would try to create everything in parallel and fail when dependent resources don’t exist yet.

Performance — Cached Knowledge
For large environments with hundreds of resources, querying the cloud API for every resource on every plan would be brutally slow. State acts as a cache — Terraform reads what it knows from the state file and only queries the cloud to detect drift (or when you force a refresh). This is why terraform plan for a 200-resource environment takes 30 seconds instead of 10 minutes.

Drift: When Reality and State Diverge

Someone logs into the AWS console and manually changes a security group rule. Now the real infrastructure no longer matches what Terraform’s state file says. The config says port 443 only. The console now also allows port 22.

That’s drift. And it happens constantly in any organization where humans have console access.

On the next terraform plan, Terraform detects this divergence. It compares the state file against the real cloud resources (via a refresh), sees the extra port 22 rule, and generates a plan to remove it — bringing reality back in line with the code.

This is the enforcement mechanism that makes IaC trustworthy. Without drift detection, your code and your infrastructure slowly diverge until nobody knows which one is correct. With drift detection, Terraform acts as the reconciliation agent: code is truth, and reality must match.

There are three common drift scenarios and what Terraform does about each:

ScenarioWhat Terraform SeesWhat Terraform Does
Manual change to an existing resourceState and reality differPlans to revert the manual change
Someone manually deleted a resourceState says it exists; reality says it doesn’tPlans to recreate it
Someone manually created a resourceTerraform doesn’t know about it at allIgnores it (it’s not in state)

That third case is important: Terraform only manages resources it knows about. If someone creates an EC2 instance manually, Terraform doesn’t touch it. It’s invisible to Terraform. This is both a feature (Terraform won’t destroy things it doesn’t manage) and a risk (manual resources can conflict with Terraform-managed ones).

The Default State File Location

By default, Terraform stores state locally:

./terraform.tfstate

It also creates a backup before every apply:

./terraform.tfstate.backup

This is acceptable for learning and experimentation. It’s genuinely dangerous in production because:

  • Single point of failure — The file is on your laptop. Laptop dies, hard drive fails, you accidentally delete it → state is gone. Terraform no longer knows what it built.
  • Concurrent access — If two people run terraform apply simultaneously against the same state file, they’ll corrupt it. There’s no locking mechanism for local state.
  • Security — The state file contains secrets in plaintext. Storing it on a developer’s laptop means secrets live outside your security perimeter.
  • Sharing — Teams can’t share a local file without manual coordination (email? shared drive? Slack?). This doesn’t scale.

The solution is a remote backend — storing state in S3 with DynamoDB locking, GCS with object versioning, Azure Blob Storage, or HCP Terraform. Remote backends provide encryption at rest, access control via IAM, state locking to prevent concurrent modifications, and versioning to recover from bad applies. Local state is only acceptable for solo, non-production work.

What You Should NEVER Do to the State File
  1. Never manually edit terraform.tfstate — The JSON structure has internal consistency requirements. A malformed state file will break all future operations. If you need to manipulate state, use terraform state commands.
  2. Never delete terraform.tfstate — Without state, Terraform loses track of everything it built. Recovery requires importing every resource individually, which is painful for environments with more than a handful of resources.
  3. Never commit terraform.tfstate to Git — It contains sensitive data. It causes merge conflicts on team projects. It’s the wrong solution for sharing state.

Your .gitignore should always include:

*.tfstate
*.tfstate.backup
.terraform/
Design Scenario: The Developer Who Deleted State

Context: A developer runs Terraform to build a staging environment with 30 resources. State is stored locally. They get a new laptop. The old one is wiped. The state file is gone.

What happens:
– terraform plan shows every resource as needing to be created — state shows nothing, config shows everything.
– terraform apply tries to create all 30 resources. But they already exist in the cloud.
– Some resources fail with conflicts (S3 bucket name already taken). Some might succeed, creating duplicates (EC2 instances). Some might error on unique constraints.
– Now you have some duplicates, some errors, and no clean state. It’s a mess.

The fix: Use terraform import to pull existing resources into a new state file, one by one. For 30 resources, this takes hours of matching resource addresses to cloud IDs. For 300 resources, it’s a multi-day project.

The prevention: Remote backend. Always. From day one. Even if it’s just you. The cost of an S3 bucket for state storage is literally pennies per month. The cost of recovering from lost state is hours or days of engineering time.

Key Takeaways

State serves five distinct purposes, and understanding each one explains why it’s so critical:
1. Mapping — connects logical resource names in config to real-world resource IDs
2. Metadata — tracks dependencies for correct ordering of creates and destroys
3. Performance — caches resource attributes to avoid querying the cloud API on every plan
4. Drift detection — enables comparison between desired state, known state, and actual state
5. Plan computation — provides the baseline for calculating what changes are needed

State contains sensitive data by design. Every attribute the cloud API returns about your resources — including passwords, keys, and connection strings — lives in the state file. This isn’t a bug; it’s how Terraform tracks the full picture of your infrastructure. The solution isn’t to try to exclude sensitive data from state (you can’t), but to store state securely using encrypted remote backends with proper access controls.

If two people apply against the same state file without locking, the second apply overwrites the first’s state changes. This is why state locking exists, and why local state files are fundamentally unsuitable for team use.