# State — Derived Rules > Derives from `domains/infrastructure-as-code/first-principles.md`. State is the cross-cutting IaC concern: P3 (State is Truth) and P8 (Remote State with Locking) live here. Referenced by `terraform.md` and `opentofu.md`. ## Why State Matters (P3 State is Truth) - The state file is the tool's memory. It records every resource it has claimed, every attribute it has set, and every dependency it has inferred. - Without state, `plan` cannot compute a diff — it would have nothing to diff against. Lose state, lose the ability to reason about infrastructure safely. - State can contain plaintext secrets (any sensitive resource attribute). Treat state as a secret-bearing artifact: encrypt at rest, restrict access, never commit it. ## Remote State is Mandatory (P8 Remote State with Locking) - Local state (`terraform.tfstate` on disk) is acceptable only for a single developer on a throwaway sandbox. Any shared or production environment uses a remote backend. - A remote backend provides: durability (state survives workstation loss), shared access (team members and CI read the same state), and locking (concurrent `apply` is serialized). - No locking = data corruption. Two `apply` runs against the same unlocked state race; the loser's changes are silently overwritten. ## Backend Comparison (P8, C4 Locality) | Backend | Locking | Encryption | Best for | Notes | |---------|---------|------------|----------|-------| | S3 + DynamoDB | DynamoDB | SSE-KMS | AWS-hosted | The canonical AWS backend; DynamoDB provides the lock | | GCS | Built-in | CMEK | GCP-hosted | Native locking via GCS object versioning | | Azure Blob | Lease | Customer key | Azure-hosted | Lease-based locking | | HTTP (remote) | Server-side | Server-side | Self-hosted / on-prem | Requires a backend server (e.g., `terraform-backend`) | | Local | None | None | Single-dev sandbox | Never for shared or prod | | Consul | KV lock | — | Consul shops | Locking via Consul sessions | | Postgres | TX | DB encryption | DBA-owned infra | Row-level locking | - Pick one backend per environment family. Mixing backends across environments fragments operational knowledge (C4 Locality). - The backend config is part of the configuration, not a runtime secret. Credentials for the backend are runtime secrets. ## State Isolation per Environment (P4 Plan Before Apply, C4 Locality) - One state per environment. Never share a single state file across dev, staging, and prod. A `plan` against a shared state crosses environment boundaries — a prod change could appear in a dev plan. - Isolation patterns: separate workspaces, separate state keys in the same backend, or separate backends entirely. Stricter isolation = safer (separate backends for prod vs non-prod). - Name state keys by environment and stack: `env:/prod/Networking`, not `prod` or `state`. ## Locking Discipline (P8 Remote State with Locking) - `terraform force-unlock` is for a stuck lock after a crashed run, not for impatience. Verify the run is actually dead before forcing. - A forced unlock without verifying the other run is dead causes the corruption the lock prevents. - In CI, set a lock timeout so a wedged job fails rather than hanging. ## Sensitive Values in State (P10 Secrets Never in Code) - Any `sensitive = true` attribute is hidden from plan output but stored in state in plaintext (unless the provider encrypts it). - Backends with at-rest encryption (S3 SSE-KMS, GCS CMEK) protect state at rest. Access to the state file itself is the boundary. - Never log, print, or commit state. Never pipe `terraform show` to a public channel. ## State Commands (P3 State is Truth) - `terraform state list` — enumerate resources in state. First step of any state investigation. - `terraform state show ` — inspect one resource's recorded attributes. - `terraform state mv` — rename a resource's address without destroying and recreating it. Use when refactoring module structure. - `terraform state rm` — stop managing a resource without destroying it. Use when handing a resource to another configuration. - `terraform state pull` / `push` — export and import state. Used in migrations (see `opentofu.md`). - `terraform import` — bring an existing resource under management by recording its state. The resource must already exist; `import` does not create. - All `state` subcommands except `list` and `show` mutate state. Treat them as changes: review the intent, run in CI where possible, and commit the resulting config change that justifies the state move. ## Drift and Reconciliation (P9 Drift is Recoverable) - `terraform plan` reports drift: resources that exist in state but were changed out-of-band, or resources in state that no longer exist in the provider. - `terraform apply` reconciles drift by bringing reality back to the declared state. - Manual changes to managed resources are the cause of drift. Treat a drift report as an incident: find who made the manual change and why, then close the access path or the gap that allowed it. - `terraform plan -refresh=false` skips drift detection. Use only when you know state is current and you want a fast plan; never use it to hide drift. ## What Violates State Discipline | Violation | Principle | |-----------|-----------| | Committed `terraform.tfstate` | P3 State is Truth, P10 Secrets | | Local state in prod | P8 Remote State with Locking | | `force-unlock` without verifying the dead run | P8 Remote State with Locking | | Shared state across environments | P4 Plan Before Apply, C4 Locality | | Unnamed state keys (`env:/prod`) | C4 Locality | | Manual change to a managed resource | P9 Drift is Recoverable | | `state rm` to "fix" a stuck resource | P3 State is Truth |