
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. Managing it well is what keeps your deployments predictable.
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. Knowing what's inside it and why is fundamental to using Terraform well.
Here's a breakdown of its key components:
output "instance_ip" { value = aws_instance.web.public_ip }) are stored here. These values can then be easily accessed by other Terraform configurations or external tools. Be cautious, as sensitive outputs (like passwords) will be stored in plain text unless explicitly marked as sensitive.aws_instance.web).provider["registry.terraform.io/hashicorp/aws"]).i-0abcdef1234567890 for an AWS EC2 instance). This is how Terraform links its configuration to the real-world object.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.
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"
dynamodb_table = "terraform-lock-table"
encrypt = true
}
}
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.
A few habits keep state management from going sideways. Here are the ones we lean on most:
terraform state commands to avoid corruption.sensitive attribute for outputs and integrate with external secret managers.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 refresh**: Updates the state file with the latest attributes from the real-world infrastructure. While terraform plan implicitly performs a refresh, running it explicitly can be useful to see if any drift has occurred before planning changes.
terraform refresh
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.
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 is usually the safer choice.
To share information between different state files, use the terraform_remote_state data source. This allows one configuration to read outputs from another, facilitating modular and interconnected infrastructure deployments.
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
# ...
}
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.
This blog has been verified for Terraform and OpenTofu
