The infrastructure as code conversation usually starts with the HCL syntax and ends with the accident, because the syntax is the easy part and the state file is the part that decides whether the practice survives contact with a team. The developer's first terraform apply writes a local terraform.tfstate on their laptop, the file that records what the configuration actually created, and the local state is the single point of failure that the second team member's apply immediately demonstrates, the two state files that both believe they are the truth, and the infrastructure that neither describes anymore. The state management discipline is the difference between the infrastructure as a program and the infrastructure as an unmaintainable maze, and this article builds the discipline properly, the remote backend and its locking, the workspace and directory strategies for environments, the drift that detects reality's divergence, the import for the estate that predates the code, and the secrets that the state file holds and the team must govern.
The remote backend and the lock that makes teams possible
The state file is the mapping between the declarative configuration and the real resources, the database of record for what terraform created, and its two properties drive every practice that follows, it is authoritative and it is shared. The remote backend provides both, the state in the object store rather than on a laptop, the locking that serializes the applies, and the S3 backend with the lock is the canonical example, the DynamoDB table or the S3 native lock that one apply holds while every other apply waits:
terraform {
backend "s3" {
bucket = "org-tfstate"
key = "network/eu-west-1/terraform.tfstate"
region = "eu-west-1"
dynamodb_table = "tf-locks"
encrypt = true
}
}
The lock is the team practice's keystone, because terraform apply is a read, a plan, an execute sequence, and the concurrent applies are the lost update the isolation article taught at the application level, the two plans both made against yesterday's state and the second apply destroying the first's intent. The lock converts the concurrency into a queue, and the discipline it demands is the pull request workflow, the plan on the branch, the review of the plan output, and the apply that holds the lock briefly, because the long held lock is the queue that blocks the incident's hotfix.
The environment strategy, directories over workspaces for real teams
The state per environment is the second structural decision, and the two built-in options have different team economics. The workspaces are the lightweight alternative, the named states against one configuration, terraform workspace select staging, and the workspace's danger is the variable indirection, the configuration that reads the workspace name to choose its parameters, the parameter matrix that the reviewer must reconstruct in their head. The directory strategy pays more files and returns more clarity, the environments as sibling directories, each with its own state, its own variables file, and its differences are visible as diffs in the repository:
environments/
staging/
main.tf # module call, staging variables
production/
main.tf # module call, production variables
modules/
network/
service/
The team practice that survives audit is the directory strategy with the shared modules, the staging and production environments as thin instantiations of the same reviewed module code, and the promotion is the pull request that changes the module's version tag, the same artifact moving from the staging state to the production state with the same review the container image article demanded. The workspace's honest role is the ephemeral, the per developer sandbox or the per test run environment, the disposable states that the directory strategy would drown in ceremony.
Drift, the divergence between the code and the reality
The drift is the practice's chronic condition, the console click that changed a security group during an incident, the vendor portal that rotated a setting, the API script that some team ran and nobody documented, and the infrastructure that the state describes diverges from the infrastructure that exists. The drift detection is terraform plan's other job, the plan run against reality with no apply, and the scheduled plan is the audit, the nightly job that writes the plan output to the team's channel, and the diff that arrives is the drift report, the resource that changed outside the code's knowledge:
# the nightly drift audit in the pipeline
terraform plan -detailed-exitcode # exit 2 = changes needed
The drift response discipline is the part that keeps the audit meaningful, the change adopted or reverted, the console fix brought into the code or the code's intent restored to the console, and the drift that is adopted without the code update is the drift that the next apply destroys, the incident's console fix gone at the next routine apply, which is the failure mode that erodes the team's trust in the whole practice. The drift budget conversation completes the discipline, the small drifts are ticketed and batched, the drift that touches production's security posture is fixed in the working day, and the budget is written down because the alternative is the nightly report that everyone learns to ignore.
Importing the estate that predates the code
The infrastructure that predates the terraform is the adoption's universal condition, and the import is the mechanism that brings the existing resources into the state without recreating them. The modern import block made the operation declarative, the configuration declares the identity of the resource it maps to, and the plan shows the import before the state absorbs it:
import {
to = aws_instance.legacy_app
id = "i-0abcd1234efgh5678"
}
The import discipline is the strangler pattern the legacy article taught, applied to the infrastructure rather than the application, one resource family imported per sprint, the imported block immediately refactored into the module structure, and the estate's un-imported remainder documented on the map so the team knows what the code does not yet own. The dangerous half of the import is the resource that the import's configuration does not fully describe, the cloud provider's fields that the state cannot express, and the discipline's rule is the plan review, the first plan after the import shows the reconciliation, and every unexpected diff is investigated rather than applied, because the import that hides a destructive diff is the adoption's catastrophic failure.
The state recovery rehearsal, the drill before the bad afternoon
The state is a single artifact that the practice cannot afford to lose, and the recovery rehearsal is the drill that keeps its loss survivable. The scenarios are specific, the corrupted state after a failed apply's write, the accidentally deleted bucket object, the lock that a crashed runner left held, and each has a rehearsed answer. The versioned bucket provides the first answer, the previous state version restored in one command, and the object store's versioning is the practice's cheapest insurance:
aws s3api list-object-versions --bucket org-tfstate --key network/eu-west-1/terraform.tfstate
aws s3api restore-object ...
The stuck lock has the forced unlock, the command that exists for the crashed runner and is documented for the human who misuses it, and the discipline is the ownership check, the forced unlock run only after confirming the holding process is actually dead, because the forced unlock against a live apply is the lost update that the lock exists to prevent. The empty state recovery is the deepest drill, the state lost entirely and the resources rebuilt from the import, the estate re-imported from the tags and the names, and the team that rehearses it once a year discovers the import coverage gaps on a quiet morning rather than during the recovery. The state recovery drill's calendar entry is the same one the backup article taught, quarterly, timed, and its findings ticketed, because the recovery that was never rehearsed is the recovery that becomes the second incident.
The module organization, the repository that stays readable
The state discipline's structural half is the code organization that the directories and modules compose into, and the readable repository has a recognizable shape. The modules are the reusable units, the network, the database, the service, each with its own versioning and its own tests, and the environments are the thin callers, the module instantiations with the variable files. The registry question arrives at the second team, the private registry or the monorepo's tagged directories, and the choice prices the same way the shared library question always prices, the tagged module consumed by the version reference is the dependency discipline that the container and pipeline articles both reached, and the module consumed by the branch reference is the drift that the version discipline exists to prevent.
module "network" {
source = "app.terraform.io/org/network/aws"
version = "4.2.1" # the tag, never the branch
}
The module's tests are the organization's other half, the terraform test framework running the module against the ephemeral infrastructure, the examples that double as documentation, and the module without tests is the module that the next consumer debugs in production. The repository's review readability completes the shape, the environment diff that a reviewer can read in one screen, the module change that the version bump documents, and the repository that stayed readable is the infrastructure that the next engineer can safely change, which is the whole practice's last mile.## The secrets in the state and their governance
The state file's honesty includes its secrets, the values that terraform must record to manage the resources, the database password passed as a parameter, the API key the provider stored, and the state in the bucket is the secret store whether the team admits it or not. The governance starts with the encryption at rest, the backend's encrypt flag, the access policy that names the engineers who may read, and it continues in the configuration discipline, the secrets sourced from the vault rather than written in the variables, the sensitive values that the plan masks and the state still records, and the rotation policy that the secret's source owns rather than the terraform state.
The working rules for the whole state discipline compress into the short list, and the list is the practice:
- Remote state with locking from the first team member, never the laptop file, never the concurrent applies;
- Directories per environment with shared modules, the workspaces for the disposable sandbox only;
- Plan in the pull request, apply on the merge, and the lock held briefly;
- Schedule the nightly drift audit, and adopt or revert every diff within the written budget;
- Import the legacy estate incrementally, and review the first plan after every import before anything applies;
- Encrypt the state, restrict the bucket, source the secrets from the vault, and rotate them where they live.
The infrastructure as code's promise is the reviewable, versioned, reproducible estate, and the state discipline is the promise's load bearing wall, the shared truth with the lock, the environments as readable directories, the drift as an audited budget and the secrets as governed data. The team that runs the discipline owns its infrastructure the way it owns its application, in the repository, under review, with the audit trail, and the team that skips it owns the unmaintainable maze, the state files scattered across laptops, the console as the real configuration, and the infrastructure that nobody can reproduce when the day arrives that they must.
The state is the practice's trust, held in a file, guarded by a lock, versioned in a bucket and rehearsed in a drill, and the team that treats it with that seriousness gets the infrastructure as code's full promise, the estate that any engineer can reproduce, any reviewer can audit and any incident can rollback, one configuration at a time.