Garage
| Category | Storage |
| Depends on | None |
| Required by | None |
| Image | dxflrs/garage:v2.3.0 (pinned - Garage publishes no latest) |
| Default namespace | default |
Every command and configuration value on this page is reasoned from Garage's own
documentation (fetched the same day, with the image pull independently confirmed
against the real registry) and written to match UIS's existing conventions. Nobody
has watched uis deploy garage run yet. If you are reading this before imac has
reported a successful deploy, treat it as a design, not a fact.
What It Does
Garage is a lightweight, S3-compatible object storage server maintained by Deuxfleurs, a French hosting cooperative. Applications talk to it with any AWS S3 SDK, the same way they would talk to MinIO or AWS S3 itself.
Why Garage, and why now. UIS deployed MinIO for this role until 2026-10-01, when
MinIO's images were withdrawn from every public registry it checked - not
account-gated, actually gone (urb-agents#1801; see
INVESTIGATE-service-minio-to-garage
for the measurement). uis deploy minio cannot succeed on a cluster with no cached
MinIO image. Terje decided the same day to replace it.
Key capabilities:
- S3 API on port
3900- buckets, objects, presigned URLs - Persistent storage - a PVC keeps the bucket across pod restarts and re-deploys
- A chosen credential, not a minted one - the bootstrap access key and secret
come from
urbalurba-secrets, the same way MinIO's root credential did - No web console - Garage ships none. See "What was left out" below
🔴 What is different from MinIO - read this before assuming parity
This is not a drop-in replacement in every respect.
| MinIO | Garage | |
|---|---|---|
| Root credential | a true root/admin key: can list, create and access every bucket | the bootstrap key is narrowed to one bucket after deploy (see the escalation below) - a different mechanism from MinIO's, not the same shape |
| Creating a new bucket | mc mb - any client holding the root key, any time | garage bucket create via the CLI inside the pod - an operator/automation action, not something an application can self-serve |
| Web console | yes, port 9001 | none |
| Cluster "layout" | not applicable (single node just runs) | normally a manual step (garage layout assign / apply) - UIS avoids this using --single-node --default-bucket, a flag pair Garage has shipped since v2.3.0 for exactly this case |
🔴 The bootstrap key is NOT scoped to one bucket by default - this was wrong in the first version of this page, and imac proved it
--default-bucket does not create a key limited to that bucket. It sets the key's account-level can_create_buckets=true flag, and nothing narrows it back down on its own. it is believed safe (urb-agents#1802): a single aws s3api create-bucket call, over the S3 API, with exactly this key - succeeded. The key could mint itself unlimited new buckets, each one it then owned with full read/write/delete. That is a root-equivalent credential, which is exactly the shape this page originally said Garage avoided.
Since 1.6.181, the setup playbook denies that permission explicitly, right after bootstrap (garage key deny --create-bucket <key>, confirmed from Garage's own CLI source - KeyPermOpt in src/garage/cli/structs.rs, tag v2.3.0 - and verified by reading the key's permissions back afterward, not by trusting the command's exit code). Before that fix, every uis deploy garage handed out a credential that could create arbitrary buckets.
⚠️ The consequence that matters most: if a second UIS consumer wants its own
bucket (today, nothing does - the Loki/Tempo migration off MinIO is a separate,
not-yet-filed piece of work), someone has to run garage bucket create +
garage key create + garage bucket allow by hand or via a new playbook task
as an operator holding cluster access, because the application-facing key
can no longer do it itself. There is no uis configure garage yet. Filed as a
gap, not built speculatively - see the investigation.
🔵 Two small Garage CLI rough edges, found while fixing the above - not UIS bugs
garage key info --show-secret=falsedoes not actually suppress the secret key in its output, despite the flag name. Treat anygarage key infooutput as sensitive regardless of this flag.garage bucket delete/unaliasgave circular "has other aliases, delete it instead" errors when imac tried removing test buckets by ID rather than by name. Deleting via the S3 API (aws s3 rb) worked without issue.
Deploy
./uis deploy garage
No dependencies.
Access after deploy:
| What | URL |
|---|---|
| S3 API (browser / apps outside the cluster) | http://s3.localhost or http://garage.localhost |
| S3 API (from inside the cluster) | http://garage.default.svc.cluster.local:3900 |
| S3 API (from the host machine) | ./uis expose garage → http://localhost:39901 |
🔵 s3.<domain> is shared with MinIO's IngressRoute on purpose - it is the
protocol name, not the implementation, so an application configured against it does
not need to change when the object store behind it does. ⚠️ Do not deploy both at
once: with both present, which one actually answers s3.<domain> is undefined.
The setup playbook warns if it finds MinIO's Service still there.
Credentials:
kubectl get secret urbalurba-secrets -n default -o jsonpath='{.data.GARAGE_ACCESS_KEY}' | base64 -d
kubectl get secret urbalurba-secrets -n default -o jsonpath='{.data.GARAGE_SECRET_KEY}' | base64 -d
Region is garage (Garage's own default; S3 SDKs require a region string even
though Garage, like MinIO, does not use it for routing).
What was left out, deliberately
- No console. Garage ships none, so there is nothing to add.
- No
[s3_web]section (Garage can serve bucket contents as a static website). Not a UIS use case today. - No
[admin]section (Garage's HTTP admin API, separate from the S3 API). Nothing in UIS calls it - bucket and key administration is done via thegarageCLI inside the pod, matching Garage's own Kubernetes cookbook (kubectl exec ... -- ./garage status). Add it if something needs it; it is not a security boundary being skipped, it is unused surface not being built. - Garage's own Helm chart. It lives only in Garage's git repository - no
Helm-repo index, no versioned
.tgzto pin, which is part of why MinIO's own chart going stale went unnoticed for so long. It also needs a CustomResourceDefinition for one discovery mode and is built for a real multi-node, geo-distributed cluster: two PersistentVolumeClaims per replica, a manual layout step after every install. None of that fits one dev-laptop instance, so UIS runs the official image directly instead.
Troubleshooting
- Check pods:
kubectl get pods -n default -l app=garage - View logs:
kubectl logs -n default -l app=garage - Check the layout configured:
kubectl exec -n default <pod> -- /garage status - Check the bootstrap bucket exists:
kubectl exec -n default <pod> -- /garage bucket list - Verify end-to-end:
./uis verify garage
Removing
./uis undeploy garage # keeps the PVC - bucket data survives
./uis undeploy garage --purge # deletes the PVC - PERMANENTLY destroys bucket data