ADR-0033: The Object Store Cairn Ships Is Garage
Context and Problem Statement
Both compose paths failed at docker compose pull (issue #388):
quay.io/minio/minio:latest, which the self-hosting guide names, answers 401 to
anonymous pulls, and minio/minio:latest plus minio/mc:latest, which the
development stack names, report that the repository does not exist. ADR-0008
chose "any S3-compatible store" for the interface and never named the store the
repository ships, so the guide and docker-compose.yml each carried an unstated
assumption about one vendor's registry, and both rotted at once. The documented
deploy path and CI's integration job had also diverged: CI has run Garage
(dxflrs/garage:v1.0.1) since the integration job existed, which is why "S3
compatible" was a tested claim only in CI while a fresh machine could not follow
the guide.
Decision Drivers
- A self-hoster copying the guide reaches a working instance on the first try, with images that pull anonymously from a registry nobody in this project controls.
- The store the documentation names is the store cairn's S3 code is actually exercised against, rather than a second vendor assumed compatible.
- Self-hosting stays possible on an existing store: ADR-0008's "Garage / MinIO / S3" surface is unchanged, and cairn's object-store code still speaks plain S3.
- Bootstrap steps belong in a script or a documented command list, not in an image that has to be pulled from somewhere.
Considered Options
- Garage, pinned to the tag CI runs. The image resolves today, and it is the
store the S3 tests have been running against all along, so the switch also
removes a second vendor whose compatibility was only assumed. Cost: Garage needs a layout, a
bucket and an imported key before it serves anything, and its image is
distroless, so the bootstrap runs the admin CLI through
docker compose execrather than as a one-shot sidecar. - Pin a MinIO image by digest from a registry that still serves one. Keeps today's zero-bootstrap experience, but no registry checked resolves anonymously, and pinning a frozen MinIO buys a maintenance debt against a project whose upstream images this issue just showed to be unreliable.
- A different self-bootstrapping S3 image (LocalStack, Scality S3, Ceph RGW). Each pulls, and each is either heavier than cairn's whole stack or a mock that does not implement the lifecycle call the staging TTL (ADR-0008) relies on. None is what CI tests.
Decision
Name dxflrs/garage:v1.0.1, the tag the integration job already runs, in both
docker-compose.yml and the self-hosting guide's compose block. A test asserts
the tag matches across those two files and .gitea/workflows/pipeline.yaml, so
the documented path and the tested path cannot drift again without failing a
build. scripts/s3-bootstrap.sh owns the layout, bucket and key steps, and is
idempotent because make up runs it on every start.
Two things follow and are open for review:
- the development stack generates a key pair into a gitignored
garage/dev-credentials.envinstead of carrying a literal pair the wayminioadminwas carried before, since a Garage secret is a 64-character hex string and a committed one is exactly what the secret scanner exists to catch; - the built-in
minioadmindefaults ininternal/configstay, because a self-hoster pointing cairn at their own MinIO still needs no configuration for credentials, and the guide now says plainly that those defaults cannot authenticate against the store it ships.
Consequences
- Self-hosting gains one documented bootstrap step that MinIO did not need. It takes seconds, it is idempotent, and it is the store's own cluster layout rather than anything cairn participates in.
- Anyone who prefers MinIO, Garage on other hosts, or S3 itself substitutes the service block and drops the bootstrap step; nothing in cairn changed to favor Garage beyond which image the examples name.
- If a future issue shows Garage's registry as unreliable as MinIO's proved to be, the same reasoning applies to whatever resolves and is tested.