Skip to main content

The state file: the file that makes Terraform smart

Summary: the state file (terraform.tfstate) is Terraform's beating heart. Without it, no plan would work. This lesson explains why it exists, what it contains, why it must absolutely not stay local in a team, and how to secure it via a remote backend with locking — a pattern that has become standard in 2026.


1. Why Terraform needs a state — the problem without state

Reminder of how Terraform works: you run terraform plan, it tells you "1 to add, 0 to change, 0 to destroy". Question: how does Terraform know what already exists and what it must change?

Three theoretical options:

The state file is the bridge between:

  • The HCL code (what you want).
  • The cloud reality (what actually exists).

Without it, every plan would be catastrophically slow and fragile.


2. What a state file looks like

A state file is a JSON file. Here is a simplified excerpt.

{
"version": 4,
"terraform_version": "1.6.0",
"serial": 42,
"lineage": "d2c7b9c3-4e50-4f81-b7b2-6b9a0c8d1e2f",
"resources": [
{
"mode": "managed",
"type": "aws_instance",
"name": "web_server",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"attributes": {
"id": "i-0abc123def456",
"ami": "ami-0c55b159cbfafe1f0",
"instance_type": "t3.micro",
"public_ip": "52.4.123.45",
"private_ip": "10.0.1.20",
"tags": {
"Name": "web-server-production",
"Environment": "prod"
}
}
}
]
}
]
}

Decoding:

  • serial — a version number that increments at every change.
  • lineage — a unique state identifier, to avoid accidental merges.
  • resources — the list of all managed resources, with their real attributes fetched from the cloud.

So the state contains:

Crucial point: the state contains all the sensitive values (passwords, API keys, tokens) that appear in your resources. This is why it must never remain in plain text.


3. The local state drama — why you cannot stay local

By default, terraform init creates a terraform.tfstate file in the working directory — on your local disk. That is convenient for learning, but unusable in a team.

Result: in a team, you need a remote backend — a secure, shared, locked, encrypted place where the state lives.


4. The remote backend — the modern solution

A Terraform backend is the mechanism that defines where the state is stored and how it is manipulated.

4.1 · The most used backends

The default choice in 2026:

  • You are on AWSS3 + DynamoDB + KMS (proven pattern).
  • You are on GCPGoogle Cloud Storage (simple and native).
  • You are on AzureAzure Blob Storage (simple and native).
  • You want zero infraTerraform Cloud (free SaaS up to 500 resources).
  • You are on GitLabnative GitLab backend (unified CI/CD workflow).

4.2 · Configuring an S3 backend

Here is what the S3 backend configuration looks like, with DynamoDB locking:

terraform {
backend "s3" {
bucket = "my-company-terraform-states"
key = "production/us-east-1.tfstate"
region = "us-east-1"
encrypt = true
kms_key_id = "alias/aws/s3"
dynamodb_table = "terraform-state-lock"
}
}

Decoding:

  • bucket — the S3 bucket where the state is stored.
  • key — the path within the bucket (allows several states in the same bucket).
  • encrypt = true — server-side encryption enabled.
  • kms_key_id — the KMS key for encryption.
  • dynamodb_table — the DynamoDB table used for locking.

What you do not see here — but which is essential — is that the S3 bucket itself must:

  • Have versioning enabled (to roll back in case of corruption).
  • Block all public access.
  • Have a lifecycle policy to delete versions that are too old.
  • Have a strict IAM role — only the CI and the admins have access.

These elementary practices protect your infrastructure. They are detailed in the Premium Terraform Course.


5. Locking — avoiding concurrent catastrophes

Without locking, two simultaneous apply runs can irreversibly corrupt your state.

With locking (DynamoDB, GCS, Azure Blob, Terraform Cloud):

Absolute rule: never Terraform in a team without a remote backend with locking. This rule is as important as "always use Git".


6. Drift — when reality diverges from the code

Drift is the phenomenon where a resource managed by Terraform is modified outside of Terraform — for example, an admin who changes a parameter by hand in the AWS console for an urgent fix.

Drift is the enemy of reproducibility. To avoid it:

  • Forbid manual modifications — the Terraform code is the only source of truth.
  • Use a detection tooldriftctl (open source) or Terraform Cloud (paid) regularly scan for discrepancies.
  • React immediately — if drift is detected, either update the code to reflect the change, or restore the desired state.

7. Terraform import — taming the existing

A frequent case: you inherit an infrastructure created by hand and you want to place it under Terraform management without recreating everything.

Solution: terraform import.

terraform import aws_instance.web_server i-0abc123def456

This command:

  1. Queries the AWS API to retrieve the current attributes of the resource i-0abc123def456.
  2. Writes these attributes into the state, associating them with the logical resource aws_instance.web_server.
  3. Then, you must write by hand the corresponding resource "aws_instance" "web_server" {} block, with consistent values.
  4. A terraform plan must then say "0 change" — otherwise you have a mismatch to fix.

Good news since Terraform 1.5: the new import syntax in HCL lets you import declaratively, versioned in Git, more reproducibly.

Typical import use cases:

This is an essential skill in large companies that adopt Terraform on an existing infrastructure — a topic explored in depth in the Premium Terraform Course.


8. Workspaces and multiple environments

The same Terraform code can create several environments (dev, staging, prod). Two main approaches:

Golden rule: prod and dev never share the same state. A mis-targeted terraform destroy can then only destroy one environment, never all of them at once.


9. The golden rule of the state — never in Git

ABSOLUTE RULE: the terraform.tfstate file must never be committed into Git.

Why:

  • It contains secrets in plain text (passwords, tokens, keys).
  • It contains the complete state of your infrastructure, precious to an attacker.
  • It creates unsolvable Git conflicts at every change.

Always add to your .gitignore:

# Terraform state files
*.tfstate
*.tfstate.*
*.tfstate.backup

# Terraform environment files
.terraform/
.terraform.lock.hcl

# Sensitive .tfvars
*.tfvars
!example.tfvars

The state must live in a secure remote backend. Period.


Remember in 30 seconds

  • The state file is Terraform's beating heart — it bridges the HCL code and the cloud reality.
  • Without the state, Terraform can neither detect changes, nor orchestrate dependencies, nor produce a plan.
  • In a team, the state must live in a remote backend (S3, GCS, Azure Blob, Terraform Cloud, GitLab).
  • Locking (DynamoDB, GCS lock, Terraform Cloud) prevents corruption from concurrent apply runs.
  • Drift is the divergence between the code and reality — to be monitored with driftctl or Terraform Cloud.
  • terraform import lets you tame an existing infrastructure without downtime.
  • ABSOLUTE RULE: the state is never in Git.

Next: Ecosystem and alternatives: OpenTofu, Pulumi, CloudFormation →