d1aa5daf2b
---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---
5.8 KiB
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 byterraform.mdandopentofu.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,
plancannot 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.tfstateon 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
applyis serialized). - No locking = data corruption. Two
applyruns 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
planagainst 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, notprodorstate.
Locking Discipline (P8 Remote State with Locking)
terraform force-unlockis 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 = trueattribute 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 showto 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 (seeopentofu.md).terraform import— bring an existing resource under management by recording its state. The resource must already exist;importdoes not create.- All
statesubcommands exceptlistandshowmutate 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 planreports drift: resources that exist in state but were changed out-of-band, or resources in state that no longer exist in the provider.terraform applyreconciles 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=falseskips 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 |