# Backend State Safety

This guide covers backend-specific state safety for Terraform and OpenTofu. Use it when configuring a backend, migrating state, handling locks, or reviewing access to state storage.

## When This Guide Applies

Load this guidance when the backend is `s3`, `azurerm`, `gcs`, `remote`, `cloud`, `pg`, `consul`, or `local`, or when work mentions backend migration, state storage, locking, force-unlock, state backup, or restore.

## Why This Matters

Terraform/OpenTofu state is the source of truth for live resource identity and often contains sensitive values. Backend mistakes can leak secrets, orphan resources, disable locking, or make a routine refactor look like a destructive replacement.

## Backend Baseline

- Use remote state for every shared, CI, or production environment
- Require locking on every apply path
- Encrypt state at rest and in transit
- Enable state versioning or point-in-time recovery where the backend supports it
- Keep backend storage and lock primitives in a bootstrap root with a separate lifecycle
- Never manage the backend bucket/container/table from the same root that uses it as its active backend
- Keep backend credentials out of checked-in backend config; prefer workload identity or CI-provided partial backend config

## Backend-Specific Checks

| Backend | Required Checks |
|---|---|
| `s3` | Bucket versioning, encryption, public access block, narrow IAM, lock mechanism configured, state key split by environment/root |
| `azurerm` | Storage account encryption, blob soft delete/versioning where available, lease-based locking, private/network restrictions, narrow data-plane RBAC |
| `gcs` | Bucket versioning, uniform bucket-level access, encryption policy, narrow IAM, prefix split by environment/root |
| `remote` / `cloud` | Workspace boundary matches blast radius, state sharing is restricted, sensitive variables are marked, applies use approved execution mode |
| `pg` | TLS, database backups, least-privilege user, lock behavior verified, connection secrets kept out of code |
| `consul` | TLS, ACLs, snapshots/backups, highly available quorum, lock/session behavior verified |
| `local` | Solo prototype only; do not use for shared, CI, or production environments |

## Migration Guardrails

- Do not combine backend migration with unrelated resource changes
- Freeze applies for the affected state before migrating
- Pull and securely store a state backup before `init -migrate-state`; do not commit it
- Record current backend type, address/key, workspace, runtime version, and actor
- Migrate the lowest-risk environment first
- After migration, compare resource addresses before/after and run a no-op plan
- Keep the old backend retained and access-controlled until restore has been tested or the rollback window has passed

Use `init -migrate-state` when moving state between backends. Use `init -reconfigure` only when intentionally accepting the configured backend without migrating existing state.

## Lock Handling

- Treat a lock as a safety signal, not an inconvenience
- Before `force-unlock`, verify the lock holder, CI run, process, and timestamp
- Never recommend `force-unlock` while an apply may still be running
- Serialize applies for shared foundation, backend, identity, and network roots

## Access and Secret Handling

- Treat state readers as secret readers
- Avoid storing plan/state artifacts in public or broad-access CI logs
- If a secret entered state, rotate the secret and use the secret remediation playbook; masking output is not enough
- Keep backend read/write permissions separate when the platform supports it

## LLM Mistake Checklist

- Suggesting `local` backend for a team, CI, or production stack
- Creating backend storage inside the same root that uses it
- Omitting a lock strategy for a shared backend
- Treating encryption as protection from anyone who can read state
- Combining backend migration with broad resource refactors
- Recommending `force-unlock` without proving no apply is active
- Deleting old backend data immediately after migration
- Hard-coding backend credentials in HCL or checked-in config

## Validation Commands

Use the active runtime (`terraform` or `tofu`) consistently:

```bash
terraform version
terraform workspace show
terraform state pull > state-backup.json
terraform state list > state-before.txt
terraform init -migrate-state
terraform state list > state-after.txt
diff -u state-before.txt state-after.txt
terraform plan -detailed-exitcode
```

Store `state-backup.json` in a secure temporary location outside the repository and delete it only after rollback is no longer needed.
