Skip to content

The declaration

Everything is driven by one map in main.tf:

buckets = {
  loki  = { size_gib = 20, namespace = "observability" }
  mimir = { size_gib = 20, namespace = "observability" }
  tempo = { size_gib = 10, namespace = "observability" }
}

The key is the Garage bucket name. size_gib becomes a Garage quota, namespace is the only namespace whose pods may read the resulting credentials.

Per entry, one apply produces:

Object Where
Bucket with max_size quota Garage
Access key <namespace>-<bucket>, non-expiring Garage
Read+write grant on that bucket for that key, not owner Garage
KV entry garage/<namespace>/<bucket>bucket, endpoint, region, access_key_id, secret_access_key OpenBao
Policy garage-<namespace>-<bucket>, read on that one path OpenBao

Plus one Kubernetes auth role per namespace, garage-<namespace>, bound to bound_service_account_namespaces = [<namespace>] and carrying every policy for that namespace's buckets. Service accounts are bound as *: any pod in the namespace, nothing outside it.

size_gib is a cap, not a reservation. The node advertises 90G with replication_factor = 1, so the quotas may sum to more than the disk — Garage will hit ENOSPC before the quota if you overcommit.