TrademarkTrademark
Features
Documentation
  1. Learning Center
  2. Terraform State & Backends: The Complete Guide

Article · part of a guide

Terraform State Files Best Practices

Learn what Terraform state is, best practices to use with state, and how to manipulate it.

Key takeaways

  1. Terraform state is the source of truth that maps your configuration to real infrastructure, storing metadata, outputs, resource IDs, attributes, and dependencies in a JSON file you must never edit manually.
  2. Remote state is essential for any team or production use because it centralizes storage, enables state locking, provides versioning for rollbacks, and avoids exposing sensitive data on local machines.
  3. Best practices include using a remote backend, enabling state versioning, separating state per environment, and keeping secrets out of state by marking outputs sensitive and using external secret managers.
  4. Terraform state commands like list, show, mv, rm, and import let you inspect and modify state safely, with terraform state rm removing a resource from state without destroying the real infrastructure.
  5. Reduce blast radius and improve performance by breaking infrastructure into smaller modular components with separate state files, and recover from corruption by restoring a versioned backup.

Terraform state is the record of what Terraform manages in the real world. It's the source of truth Terraform checks before it plans or applies a change, so without it Terraform can't tell what already exists. Beginners tend to treat state as an implementation detail they can ignore, and that's usually where the trouble starts.

How should a team manage Terraform state?

A team should keep Terraform state in one remote backend with locking and versioning turned on, split it into a separate state file per environment and component, and let only a CI pipeline or a managed runner apply changes. Give each team write access to its own state and read access only to the outputs it consumes. Nobody should keep a copy on a laptop.

Each of those rules exists because of a specific way shared state breaks:

Practice What goes wrong without it How to set it up
Remote backend with locking Two applies write the same state at once and one set of changes is lost S3 with use_lockfile = true, Azure Blob Storage, Google Cloud Storage, or a remote-operations backend such as HCP Terraform or Scalr
Versioning on the state store A bad apply or a corrupted file leaves you with nothing to roll back to S3 bucket versioning, blob versioning, or the state history your platform keeps
One state per environment and component A dev change touches production, and plans slow down as one file grows Separate directories or workspaces, each with its own backend key
Applies only from CI or a runner Someone applies from a laptop with stale code or personal credentials Pipeline or platform runs using short-lived cloud credentials
Access scoped per team One team can read another team's secrets straight out of state Bucket or workspace permissions per team, with values shared through outputs

The last row matters more than it looks. State stores resource attributes in plain text, so read access to a state file is often read access to database passwords and keys that ended up in it. The sections below cover what state holds, how to configure a backend, and the commands for changing state safely.

What is Terraform State?

Terraform state maps your configuration to the actual resources it manages. It tracks metadata, resource IDs, attributes, and dependencies so Terraform can understand how resources relate and apply updates in the right order. The data is stored as JSON, in a file named terraform.tfstate by default. That file also holds your outputs, and it can contain sensitive data if you aren't careful. Never edit it by hand.

State File Components

A state file has these parts:

  • Metadata: The state file begins with metadata about the state format itself and the Terraform version that last updated it. Terraform uses it to read the file correctly and check compatibility.
  • Outputs: Any outputs you define in your Terraform configuration (e.g., output "instance_ip" { value = aws_instance.web.public_ip }) are stored here, where other Terraform configurations or external tools can read them. Be cautious: output values, passwords included, are stored in plain text in state even when marked sensitive, which only hides them from CLI output.
  • Resources Array: This is the most important part of the state file. It contains a list of all resources that Terraform is currently managing. For each resource, you'll find:
    • Resource Type and Name: A clear identifier linking to your Terraform configuration (e.g., aws_instance.web).
    • Provider Information: Details about the Terraform provider used to manage the resource (e.g., provider["registry.terraform.io/hashicorp/aws"]).
    • Instance Details: Each resource instance (if there are multiple) will have its own entry. This includes:
      • Unique ID: The actual ID of the resource as assigned by the cloud provider (e.g., i-0abcdef1234567890 for an AWS EC2 instance). This is how Terraform links its configuration to the real-world object.
      • Attributes: All the attributes of the resource, including those you defined in your configuration and those automatically assigned by the provider (e.g., public IP, ARN, security group IDs, instance state). These attributes represent the current known state of the resource.
      • Dependencies: Implicit or explicit dependencies between resources. Terraform uses them to work out the order in which resources must be created, updated, or destroyed.

Local vs. Remote State: Remote is King

Local state causes real problems once more than one person is involved. You hit merge conflicts, the file is easy to delete or corrupt by accident, there's no versioning to fall back on, and sensitive data ends up sitting on individual laptops. A remote backend fixes all of that. It keeps state in one shared place, locks it so two people can't write at once, versions it so you can roll back, and stores it somewhere more durable and secure than a developer's machine. For any team or production workload, remote state is essential.

Choosing a Remote Backend

Several remote backends are available for Terraform state. Popular choices include AWS S3, Azure Blob Storage, Google Cloud Storage, Scalr, and Terraform Cloud/Enterprise.

Here’s how you might configure some:

AWS S3 Backend Example:

terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "path/to/my/infra.tfstate"
    region         = "us-east-1"
    use_lockfile   = true
    encrypt        = true
  }
}

Older examples lock S3 state with a dynamodb_table argument. As of September 2026, HashiCorp's S3 backend documentation marks DynamoDB-based locking as deprecated and due for removal in a future minor version, so new Terraform configurations should use use_lockfile, which writes a lock file next to the state in the same bucket. You can set both arguments while you migrate an existing backend. On OpenTofu, check the S3 backend docs for the version you run before switching.

Scalr Example:

terraform {
  backend "remote" {
    hostname = "<account-name>.scalr.io"
    organization = "<scalr-environment-name>"

    workspaces {
      name = "<workspace-name>"
    }
  }
}

When you pick a backend, weigh cost against the cloud provider you already use, how familiar your team is with the service, and whether you need extras like policy as code or richer collaboration features.

What are the best practices for Terraform state files?

Use a remote backend with locking, turn on versioning, never edit the file by hand, split state by environment and component, and keep secrets out of it. The same rules apply to a solo project, just with less at stake:

  • Always use a remote backend: Centralize your state (e.g., AWS S3 with lock-file locking, Azure Blob Storage, Terraform Cloud) for team collaboration, state locking, and durability.
  • Enable state file versioning: Allow for rollbacks and an audit trail of all state changes.
  • Avoid manually editing the state file: Rely solely on Terraform's terraform state commands to avoid corruption.
  • Separate state files: Isolate state for different environments (dev, staging, prod) or logical components to reduce error impact.
  • Avoid sensitive data in state: Don't store secrets directly in the state file; use the sensitive attribute for outputs and integrate with external secret managers.

State Manipulation Commands with Examples

Terraform ships commands for working with the state file directly. They let you inspect and modify the resources Terraform tracks without ever opening the JSON yourself. Go slowly here: these commands change what Terraform believes it manages, and a wrong move is hard to undo.

terraform plan -refresh-only: Shows how the state file differs from the real-world infrastructure without changing any resources, which is the quickest way to check for drift before planning changes. Running terraform apply -refresh-only then writes those updated attributes into state. This replaces terraform refresh, which HashiCorp's documentation lists as deprecated.

terraform plan -refresh-only

terraform state rm <resource_address>: Removes a resource from the state file. This does not destroy the actual infrastructure resource. Use this with extreme caution when you want Terraform to "forget" about a resource it no longer manages, perhaps because it's now managed manually or by another process.

terraform state rm aws_instance.web

terraform state mv <source_address> <destination_address>: Moves a resource within the state. This is useful when refactoring your Terraform configuration, such as moving a resource into a module.

# Before: aws_instance.old_name
# After: module.web_server.aws_instance.new_name
terraform state mv 'aws_instance.old_name' 'module.web_server.aws_instance.new_name'

terraform state show <resource_address>: Displays the attributes of a specific resource as recorded in the state.

terraform state show aws_instance.web

Example Output (partial):

# aws_instance.web:
resource "aws_instance" "web" {
    id                          = "i-0abcdef1234567890"
    ami                         = "ami-0abcdef1234567890"
    instance_type               = "t2.micro"
    // ... other attributes
}

terraform state list: Shows a list of all resources tracked in the current state file.

terraform state list

Example Output:

aws_instance.web
aws_vpc.main

terraform import: Imports an existing resource into the state file. For the full workflow, including the modern import {} block, generating Terraform code from existing resources, and bulk-import patterns, see the Terraform import guide.

terraform import aws_instance.example i-abcd1234

Each of these gives you a controlled way to change state, which is far safer than editing the file by hand.

Advanced State Management: Workspaces and Isolation

As environments get more complex, how you isolate state starts to matter. Terraform Workspaces can split environments like dev and staging within a single configuration, using commands such as terraform workspace new <name> and terraform workspace select <name>. They're convenient, but they don't give you much separation. For real isolation and a smaller blast radius, separate directories with their own remote backend per environment or component are usually the safer choice.

To share information between different state files, use the terraform_remote_state data source. It lets one configuration read outputs from another, which is how you keep your infrastructure modular and still wire the pieces together.

data "terraform_remote_state" "network" {
  backend = "s3"
  config = {
    bucket = "my-network-state-bucket"
    key    = "network.tfstate"
    region = "us-east-1"
  }
}

resource "aws_instance" "web" {
  subnet_id = data.terraform_remote_state.network.outputs.web_subnet_id
  # ...
}

Common Pitfalls and Troubleshooting

Even with good habits, things go wrong. State can get corrupted by a network blip, a hand edit, or a process that dies mid-run. Recovery usually means restoring a versioned backup, and manual repair only when you have no other option. State locking handles most concurrency problems before they start.

Drift detection matters too: terraform plan shows you where your state and the real infrastructure have diverged. To keep secrets out of state, mark outputs with the sensitive attribute and lean on external secret managers rather than storing them inline. Large state files also drag down performance, so break big configurations into smaller modular components, each with its own state file. The drift detection guide goes deeper if you want it.

Most of these habits come built in when a managed platform holds your state and runs your plans: remote backend, versioned state, locking, and per-environment separation are the default rather than something you assemble from storage buckets, lock settings, and wrapper scripts. Scalr provides that on usage-based pricing that's free up to 50 runs a month.

This blog has been verified for Terraform and OpenTofu.

Frequently asked questions

How should a team manage Terraform state?

Keep state in one remote backend with locking and versioning turned on, split it into a separate state file per environment and component, and let only a CI pipeline or a managed runner apply changes. Give each team write access to its own state and read access only to the outputs it consumes. Nobody should keep a copy on a laptop.

Does the Terraform S3 backend still need DynamoDB for state locking?

No. As of September 2026, HashiCorp's S3 backend documentation marks DynamoDB-based locking as deprecated and due for removal in a future minor version. Set use_lockfile = true and Terraform locks the state with a lock file in the same bucket. Both settings can be configured together while you migrate.

Is terraform refresh deprecated?

Yes. HashiCorp's documentation lists terraform refresh as deprecated and recommends adding the -refresh-only flag to terraform plan or terraform apply instead. A refresh-only plan shows how state differs from the real infrastructure without changing any resources.

Should each environment have its own Terraform state file?

Yes. Separate state per environment, and per component inside an environment, keeps a dev change from touching production and keeps plans fast. Use separate directories or platform workspaces, each with its own backend key, and read values across them with outputs rather than sharing one large file.

About the author

Ryan Fee

director of platform engineering at Scalr

Ryan Fee is the director of platform engineering at Scalr, with over 15 years of experience improving infrastructure experiences at companies large and small.

Part of this guide

19 sheets

Terraform State & Backends: The Complete Guide

18 articles