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

77 lines
5.8 KiB
Markdown

# 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 |