Files
atelier/domains/infrastructure-as-code/state.md
T
Jon Chery d1aa5daf2b docs(milestone): complete v0.2 — infrastructure-as-code + kubernetes
---ci---
project: atelier
phase: 5
milestone: v0.2
status: complete
requirements:
  covered: [ATELIER-36, ATELIER-37, ATELIER-38, ATELIER-39, ATELIER-40, ATELIER-41, ATELIER-42, ATELIER-43, ATELIER-44, ATELIER-45, ATELIER-46, ATELIER-47, ATELIER-48, ATELIER-49, ATELIER-50, ATELIER-51, ATELIER-52, ATELIER-53, ATELIER-54, ATELIER-55, ATELIER-56, ATELIER-57, ATELIER-58, ATELIER-59]
  partial: []
---/ci---
2026-08-05 02:20:17 +00:00

5.8 KiB

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. The state file itself is a secret-bearing artifact — treat it per domains/security/secrets.md: encrypt at rest, restrict access, never commit it.

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 <addr> — 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