Skip to content

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; only backend.hcl.example is 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_rules Deny 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