0004. Terraform remote state on Azure Blob
- Status: Accepted
- Date: 2026-05-25
- Formalizes: D3, D22
- Deciders: Sagar, SnowOps engineering
Context
Terraform state must live somewhere durable, lockable, access-controlled, and auditable. We are Azure-first (ADR-0001), so the state backend should not introduce a second cloud or a third-party SaaS dependency. State contains sensitive material, so encryption, immutability, and identity-based access are required from minute one (Day-Zero Hardening).
Decision
We will store Terraform remote state in Azure Storage Blob, hardened as follows:
- Redundancy: RA-GZRS (geo-zone-redundant, read access).
- Locking: native blob-lease locking (no external lock table).
- Immutability: blob immutability/versioning enabled.
- Access: AAD-only —
use_azuread_auth = true, shared-key access disabled (D22). No storage account keys in CI; data-plane RBAC is granted by B4. - Backend partial-config (
backend.hcl) is gitignored; onlybackend.hcl.exampleis committed.
Consequences
- Easier: one cloud, one identity model; state access is governed by the same Azure RBAC + PIM as everything else, and every access is logged.
- Easier: blob-lease locking removes the need for a separate lock store (unlike the S3 + DynamoDB pattern).
- Harder / accepted: GitHub-hosted runners need network reachability to the storage
endpoint, so storage-account network lockdown is opt-in (default off) — the
network_rulesDeny is the lever when self-hosted runners are available (D22). - Harder / accepted: AAD-only auth means any consumer must hold the right data-plane role; misconfigured RBAC fails closed.
Alternatives considered
- Terraform Cloud / HCP: rejected — adds a SaaS dependency and another identity boundary for state that already belongs in the client's Azure tenant.
- S3 + DynamoDB: rejected — off-cloud for an Azure-first product; two services where one suffices.
- Storage account with shared keys: rejected — violates Identity > Secrets (ADR-0003).
References
modules/azure/state-backend module (F6), B4 (data-plane RBAC)- ADR-0003 (OIDC federation), ADR-0001 (Azure-first)
- Decisions D3, D22 in
docs/context/07-decisions.md