SeaweedFS was scoped as primary because it looked like the only candidate preserving OIDC console login. Measured: its admin UI is Apache-2.0 but its identity-provider integration is not — console SSO sits behind the per-TB commercial licence, alongside point-in-time recovery and automatic EC repair. The free build gives OIDC on the S3 API via STS and a console authenticated by local username and password. So the answer to the gating question is that no candidate preserves the current feature set for free, which this effort had written down as a possible outcome. Reopened across three candidates with the requirement-by-requirement evidence in 01. Two corrections to what the overview recorded. RustFS is not a binary-level drop-in retaining existing data: API and on-disk compatibility are separate paths and the on-disk one is preview-scoped. And it carries an open defect in the credential path the bucket provision depends on, which gates it specifically. Nothing graduates before two measurements named in 01: whether an authenticating proxy is an acceptable answer to console SSO, and which S3 endpoints consumers actually call — the latter because Garage does not implement the full span and cannot be ranked until that is counted.
7.9 KiB
status, initiated, touches
| status | initiated | touches | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| active | 2026-09-24 |
|
015 — The object store after MinIO: which S3 implementation, and how the data moves
The question. The mesh's object store is MinIO. Its community edition is archived upstream, its server and client images have been deleted from every public registry, and the pinned release is four and a half years old and will never be patched (issue 113). Which S3-compatible implementation replaces it, and what is the migration track for the data and the provisioning model that sit on top of it?
Why now, and why not sooner. Nothing is on fire: nodes that already hold the images keep running, and issue 113 establishes that the deploy path tolerates an unfetchable-but-present image by design. The forcing function is not an outage but a one-way door — no node that does not already hold the images can ever provision the module again, so the mesh's ability to stand a node up from its declarations is already broken for this module, and silently.
The direction is not a departure from the design; it is the design. The foundation document already states the commitment:
The dependency is on the protocol, not the product: AMQP for the bus, S3 for the object store, the OCI protocol for the registry. That is what keeps the naming safe rather than a commitment that cannot be revisited.
The object store is also not a foundation service — ADR 0028 removed it, and it is an ordinary module required through the module graph by whatever wants one. (The "exception that is not a swap" in that passage is the relational store, whose provisioning model borrows PostgreSQL's own meaning of databases, roles and schemas. The object store carries no such coupling: a bucket is a bucket.) So this effort is an instantiation of an existing principle, not a redesign — which is the cheapest kind of decision to make and the strongest kind to cite.
What the replacement has to carry, measured
Taken from the module's manifest, its composition, its tool surface, and a search for its consumers across the catalogue — not from assumption.
| Requirement | Evidence in the module today |
|---|---|
| S3 API | The protocol every consumer speaks; already the design's stated dependency. |
| OIDC login against the mesh's identity provider | Six configuration variables are wired and populated in practice — discovery URL, client id, client secret, scopes, display name, redirect — plus a dedicated entrypoint script that blocks startup until the provider answers. This is live, not aspirational. |
| Erasure-coded multi-node topology | Four server nodes with two data directories each, behind a load balancer. |
| A single-node form | Declared as a flavour, for development and small nodes. |
| Buckets as a typed provision | The module declares a provision type of bucket on a named network; the mesh mints the credential and the provider creates it (ADRs 0048, 0084). |
| A tool surface | Bucket create/list/delete, object list/info/delete, presigned URL, and provisioning. |
| A console | Published on its own subdomain through the reverse proxy, with an unlimited request-body middleware for uploads. |
Consumers, counted: one application module, one capture module that takes a private bucket per node, one workflow module's tools, and the delivery/rescue internals of the shared library. The surface is small — the cost is concentrated in the provisioning handler, the tool handlers and the OIDC story, not spread across the catalogue.
Candidates
The comparison is open across three candidates. It was briefly narrowed to SeaweedFS; that narrowing did not survive measurement and was reopened on 2026-09-24. The evidence, the full requirement-by-requirement table and what each option costs are in 01 — the candidates measured.
In short, and only in short:
- SeaweedFS — Apache-2.0, the longest field record, erasure coding. Its console OIDC is an Enterprise feature; the free build authenticates the console with a local username and password. This is what falsified the original narrowing.
- Garage — the best match for how the mesh provisions, with a first-class admin REST API scoped to exactly bucket and key creation. Costs: replication rather than erasure coding, no full S3 endpoint span, and neither console nor identity integration.
- RustFS — the only candidate preserving the live behaviour, with a MinIO-shaped console and documented OIDC against Keycloak, under Apache-2.0. Costs: it reached GA eight days before this was written, and an open defect is reported in the credential path the mesh's bucket provision depends on.
- Ceph RGW — remains rejected as disproportionate where the object store is an ordinary module rather than a platform.
The question that decided the original narrowing has been answered — SeaweedFS's console OIDC is not in the free build — and the answer was "no candidate preserves the current feature set for free", exactly the outcome this effort said was possible. The decision is therefore not which product is best in the abstract but which cost is acceptable, and two measurements gate it: whether an authenticating proxy is an acceptable answer to console single sign-on, and which S3 endpoints consumers actually call. Both are named in 01. Nothing graduates before them.
The migration track, in outline
Data movement is the easy half, and deliberately reversible.
- Stand the replacement up beside the incumbent, on its own ports and its own provision type. No downtime, nothing removed.
- Copy bucket by bucket with a neutral tool.
rclonerather than the incumbent's own client — the client has been withdrawn upstream too, so building the migration on it would inherit the same dependency this effort exists to remove. - Verify per bucket — object counts and checksums, not a transfer exit code.
- Repoint consumers through the connection the module already publishes. Consumers read an API URL from the module's declared connections rather than addressing the store directly, so the cutover surface is that value plus the provisioning and tool handlers.
- Freeze writes, final incremental sync, flip, and keep the incumbent read-only as the rollback until confidence is earned.
- Retire, and only then remove the module.
The genuinely new work is not the copy. It is the provisioning handler and the tool handlers, which are written against MinIO's admin API, and the OIDC wiring.
Open questions
- How much of the OIDC requirement survives, and in which build? See above — this gates the choice.
- Does the mesh's bucket provision translate to the candidate's identity model without weakening what ADR 0049 says about a consumer's identity fitting the tightest backend?
- Should this effort also answer issue 113's general question — mirroring third-party images into the mesh's own registry — or is that a separate decision? Replacing one withdrawn product with another unmirrored upstream leaves the same one-way door in place, just further from the hinge.
- Is the four-node erasure-coded topology still warranted, or was it inherited? Worth re-asking while the product is being chosen, rather than reproducing a shape by default.