Declare a bucket in catalog-info.yaml (catalog 0.3.0+). The builder provisions a MinIO
bucket with a hard quota, and credentials are injected into your pod by Vault — dynamic,
short-lived, scoped to your bucket. Use the @tawa/storage SDK.
metadata:
annotations:
insureco.io/catalog-version: "0.3.0"
spec:
storage:
- name: uploads # → S3_UPLOADS_BUCKET (+ S3_BUCKET alias if single-bucket)
tier: s3-md # s3-sm | s3-md | s3-lg | s3-xl
import { createStorage } from '@tawa/storage'
const storage = createStorage() // reads S3_* env vars (single bucket)
await storage.upload('report.pdf', buffer)
| Tier | Capacity | Gas / month |
|---|---|---|
s3-sm | 1 GB | 200 |
s3-md | 5 GB | 800 |
s3-lg | 25 GB | 3000 |
s3-xl | 100 GB | 10000 |
Credentials are dynamic MinIO users minted by a custom Vault secrets engine. Pods never hold a static key:
minio/creds/{role} → the plugin creates an ephemeral MinIO
user scoped to your bucket, with a lease (default_ttl: 1h, max_ttl: 24h)./vault/secrets/storage and sourced into process.env.This is self-consistent: Vault always creates the MinIO user at the same moment it issues the credential, so MinIO, Vault, and the pod stay in sync.
The roll above only fires on Vault's lease clock. An out-of-band MinIO IAM reset — a
reinstall, a .minio.sys wipe, or a volume loss — is invisible to Vault: MinIO loses every
Vault-created user, but Vault still holds the leases and believes they're valid, so no roll
fires. Every storage pod keeps serving cached creds that now throw InvalidAccessKeyId,
silently, until max_ttl.
You cannot fix this inside the roll: the roll is time-driven and the reset has no signal into Vault. The builder runs a storage reconciler that supplies that signal from outside.
Every STORAGE_RECONCILE_INTERVAL_MS (default 2 min) the builder:
InvalidAccessKeyId / SignatureDoesNotMatch) while admin still works, MinIO has
forgotten an account Vault issued — an IAM reset.STORAGE_CANARY_FAILURE_THRESHOLD (default 3) consecutive failures
before acting, so a transient blip never restarts the fleet.On a confirmed reset (and outside STORAGE_HEAL_COOLDOWN_MS), it heals every storage service:
re-asserts the Vault role, recreates buckets if also wiped, revokes the stale Vault leases, and
kubectl rollout restarts the deployment. On restart the Vault Agent re-reads creds, the plugin
re-creates the MinIO user, and the pod recovers. A Septor storage.iam_reset_healed event and an
alert email are emitted. The number of services healed per run is capped by
STORAGE_HEAL_MAX_SERVICES (default 50).
Non-destructive: the heal only touches the identity layer (Vault leases, MinIO users, pod lifecycle). Object data is never deleted. If a reset also wiped object data, buckets are recreated empty — the reconciler restores access, not lost objects. Object durability is MinIO's job (distributed/erasure-coded mode + backups).
When ops knowingly reset MinIO, freshly-minted canary creds would mask the drift, so detection is bypassed — call the platform-admin endpoint to re-provision the whole fleet immediately:
POST /storage/reconcile { "reason": "minio reinstall 2026-06-24" } # platform-admin
GET /storage/health # last reconciler result
| Env var | Default | Purpose |
|---|---|---|
STORAGE_SELF_HEAL_ENABLED | true | Master switch for the reconciler loop |
STORAGE_RECONCILE_INTERVAL_MS | 120000 | Time between detection passes |
STORAGE_CANARY_FAILURE_THRESHOLD | 3 | Consecutive canary failures before healing |
STORAGE_HEAL_COOLDOWN_MS | 900000 | Minimum gap between auto-heals (anti-thrash) |
STORAGE_HEAL_MAX_SERVICES | 50 | Per-run restart cap |
STORAGE_CANARY_BUCKET | iec-storage-canary | Canary probe bucket |
PLATFORM_ALERT_EMAIL | (unset) | Recipient for heal alert emails (no email if unset) |
The durable long-term fix is to federate storage identity (MinIO OIDC backed by Bio-ID) so there is no stored MinIO user to lose at all — the same model AWS IRSA / GCP Workload Identity use. The reconciler remains the right safety net even after that lands.
POST /storage/reconcile).spec.storage below catalog 0.3.0 — the field is ignored.Last updated: July 15, 2026