All guides
Practice now
D6 · Terraform State

Terraform state: purpose, locking, and drift

State is the core of how Terraform works. Understanding its structure, the locking mechanism for teams, and how to handle drift are recurring topics on the exam.

What is state and what is it for?

The (terraform.tfstate) is the source-of-truth record Terraform keeps about the it manages. It's a JSON file that maps each resource in your configuration to its corresponding real resource in the cloud.

Config → real mapping

Associates aws_vpc.main with vpc-0abc123 in AWS. Without this mapping, Terraform doesn't know which resources to manage.

Diff calculation

To generate the plan, Terraform compares the state with the desired configuration. Without state, it would recreate everything.

Dependency metadata

Stores dependencies between resources to know the correct destroy order.

Attribute cache

Stores computed attributes (such as IDs generated by the cloud) for use in other references.

⚠️ Never edit the tfstate manually

Manually editing terraform.tfstate can corrupt the state and cause Terraform to unexpectedly destroy or recreate resources. Always use the terraform state commands to manipulate the state.

Structure of the tfstate file

The tfstate file is JSON. Knowing its structure helps you understand how Terraform works internally.

{
  "version": 4,                    // state format version
  "terraform_version": "1.6.3",    // version of Terraform that generated this state
  "serial": 7,                     // serial number (increments on each apply)
  "lineage": "abc-123-...",        // unique UUID of this state instance
  "outputs": {
    "vpc_id": {
      "value": "vpc-0abc123def",
      "type": "string",
      "sensitive": false
    }
  },
  "resources": [
    {
      "module": "module.vpc",      // if it's a child module, otherwise empty
      "mode": "managed",           // "managed" (resource) or "data" (data source)
      "type": "aws_vpc",           // resource type
      "name": "main",              // local name
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [               // usually 1, multiple with count/for_each
        {
          "schema_version": 1,
          "attributes": {          // ALL of the resource's attributes (including sensitive ones)
            "id":         "vpc-0abc123def",
            "cidr_block": "10.0.0.0/16",
            "tags": {"Name": "my-vpc"}
            // ... all other attributes
          },
          "dependencies": [        // resources it depends on
            "aws_internet_gateway.main"
          ]
        }
      ]
    }
  ]
}

terraform.tfstate

The main state file. Updated after every apply.

terraform.tfstate.backup

Automatic backup of the state prior to each apply. Only the most recent one.

State locking

State locking prevents two Terraform operations from modifying the state simultaneously, which could corrupt it. Terraform tries to acquire the lock before any operation that writes to the state.

BackendSupports locking?Lock mechanism
Local (default)✅ YesOS file lock — only protects on the same machine
S3✅ Yes** Requires use_lockfile = true (1.11+); DynamoDB is the legacy method
Azure Storage✅ YesNative Azure blob lease (automatic)
GCS✅ YesNative GCS object locking (automatic)
HCP Terraform✅ YesAutomatic locking on every run (plan/apply)
Terraform Enterprise✅ YesAutomatic locking
HTTP backend⚠️ OptionalDepends on the HTTP server implementation

💡 S3 locking: use_lockfile

The S3 backend does not lock state by default. Since Terraform 1.11 you enable it with use_lockfile = true: Terraform creates a .tflock file next to the state in the same bucket. The previous method, a DynamoDB table keyed on LockID referenced with dynamodb_table, is deprecated.

# If an apply fails and the lock is left "stuck":
# Terraform shows the Lock ID in the error message
terraform force-unlock LOCK_ID

# ⚠️ Only use this if you're SURE no other apply is in progress
# Incorrectly releasing the lock can cause state corruption

Drift and how to detect it

occurs when the actual state of infrastructure in the cloud differs from the state recorded in tfstate. This happens when someone modifies the infra manually (via console, cloud CLI, etc.) without going through Terraform.

Drift example

Your tfstate says the instance has instance_type = t3.micro. Someone changed it manually to t3.large from the AWS console.

Risk

If you run terraform apply without detecting the drift, Terraform may revert the manual change or produce unexpected conflicts.

Solution

Run terraform plan -refresh-only to detect and reconcile the drift without applying configuration changes.

# Detect drift (refreshes the state against reality)
terraform plan -refresh-only

# Possible output:
# ~ aws_instance.web
#     ~ instance_type = "t3.micro" -> "t3.large"  (manual change detected)

# Accept the drift (update the state to match reality):
terraform apply -refresh-only

# Revert the drift (apply the configuration as it was):
terraform apply

# A normal terraform plan also refreshes the state before planning
# The difference is that -refresh-only ONLY refreshes, it does not apply config changes

Sensitive data in the state

The tfstate stores all of a resource's attributes in plain text, including passwords, API keys, certificates, and other secrets. This is one of the most important reasons to use a remote with encryption.

High

Local backend (default)

The tfstate stays on the developer's disk. If it gets pushed to Git by mistake, all secrets are exposed.

Medium

S3 without encryption

The tfstate on S3 without server-side encryption is accessible to anyone with access to the bucket.

Low

S3 with SSE-KMS

Encrypted at rest with KMS. Access controlled by IAM policies. Recommended for production.

Low

HCP Terraform

Automatic encryption at rest. Access controlled by teams and workspace permissions.

🎯 For the exam

  • • The tfstate stores secrets in plain text regardless of sensitive = true
  • • The local backend locks with OS files, but is not suitable for teams
  • • The S3 backend locks with use_lockfile = true (DynamoDB is deprecated)
  • • terraform plan -refresh-only detects drift without applying changes
  • • Never commit the tfstate to VCS

Did you get this topic?

Test what you just learned

A team uses the S3 backend to store Terraform state. They have configured the bucket correctly but notice that two team members can run terraform apply at the same time without one blocking the other. What is missing from the configuration?

Ready to test "Terraform State: Purpose, Locking and Drift"?

Put what you learned to the test with original Terraform Associate exam questions.

Start Terraform Associate mock exam

Unlimited mock exams with the Pro plan

Log in to track your progress.