Silo vs Garage vs RustFS: Migrating Off MinIO in 2026
MinIO's repository is archived and the community edition ships as source only. Here are the three S3-compatible replacements, tested side by side on real containers: feature gaps, memory, startup time and what a migration actually costs.
MinIO is no longer a safe default: its repository was archived in April 2026 and the community edition is distributed as source code only. This guide puts the three realistic replacements on the same host and measures them — Garage v2.4.1, Silo and RustFS 1.0.3 — with the same boto3 client, the same bucket and the same feature probes. All three pass a basic S3 round trip; they differ sharply on versioning, bucket policies, memory and operational surface. Verified 2026-10-19.
The short answer
| Garage | Silo | RustFS | |
|---|---|---|---|
| License | AGPL-3.0 | AGPLv3 | Apache-2.0 |
| Pinned tag tested | dxflrs/garage:v2.4.1 |
pgsty/silo:RELEASE.2026-10-18T00-00-00Z |
rustfs/rustfs:1.0.3 |
| S3 GET/PUT/LIST | yes | yes | yes |
| Presigned URLs, multipart, copy | yes | yes | yes |
| Versioning | no (NotImplemented) |
yes | yes |
| Object tagging | no | yes | yes |
| Bucket policy (public read) | no | yes | yes |
| Object lock | accepted, silently dropped | enforced | enforced |
| Web console | none | yes, :9001 |
yes, /rustfs/console/ |
| Idle memory (cgroup) | 5.0 MiB | 72.9 MiB | 55.5 MiB |
| First successful S3 call | 0.38–0.40 s | 0.60–0.84 s | 0.45–0.46 s |
Garage is the lightest and fastest to start by a wide margin and gives up the most S3 semantics. Silo is the closest thing to a MinIO drop-in, including a console and a health endpoint that mirrors MinIO’s. RustFS sits between them with an Apache-2.0 license that neither of the other two offers.
Why this migration exists
Verified on 2026-10-19, not from memory:
GET https://api.github.com/repos/minio/minio→"archived": true, last push2026-04-24T17:54:39Z, licenseAGPL-3.0.- The README states: “THIS REPOSITORY IS NO LONGER MAINTAINED” and “The MinIO community edition is now distributed as source code only.”
https://hub.docker.com/v2/repositories/minio/minio/tags→ 404;docker pull minio/minio:latest→ “pull access denied … repository does not exist”.https://quay.io/v2/minio/minio/tags/list→ 401 anonymously, and a pull prompts for a login.
So the usual docker run minio/minio is not available to a new deployment. You either move to another S3 implementation or accept a source build.
How the comparison was run
Same host, same client, same probe script — that is the only way the numbers mean anything:
- 12 vCPU shared host, 16 GB RAM, Docker 28.2.2, single node per product.
- Each server in its own container, one bucket, one credentials pair per server.
- Client:
boto3 1.43.114, path-style addressing, SigV4,region_name="us-east-1",retries={"max_attempts": 1}. - Payload written and read back:
runcheap verification payload 2026-10-19\n(41 bytes).
The round trip is identical on all three, down to the ETag:
create_bucket: BucketAlreadyOwnedByYou (continuing)
put_object: ETag="98d73e53736bbc174fb1f903dda25c31" HTTP=200
get_object: HTTP=200 bytes=41 match=True
list_objects_v2: keys=['verify/hello.txt', 'verify/multipart.bin', ...] count=3
A shared ETag for the same bytes is the expected result, not a coincidence: MD5 of a 41-byte object is not implementation-specific.
Feature gaps, measured not assumed
Each probe was a real API call with max_attempts: 1, so a retry cannot mask a failure:
| Probe | Garage | Silo | RustFS |
|---|---|---|---|
PutBucketVersioning |
NotImplemented - Unimplemented action: PutBucketVersioning |
OK | OK |
PutObjectTagging |
NotImplemented - Unimplemented action: PutObjectTagging |
OK | OK |
PutBucketPolicy (public read) |
NotImplemented - Unimplemented action: PutBucketPolicy |
OK | OK |
Anonymous GET after policy |
n/a | HTTP 200 | HTTP 200 |
Object lock GOVERNANCE until 2030-01-01 |
accepted, head_object returns mode None |
enforced | enforced |
| SSE-S3 | accepted and silently ignored | requires an external KMS | requires RUSTFS_SSE_S3_MASTER_KEY |
The object-lock row is the dangerous one. Garage does not return an error when you ask for a retention policy — it accepts the parameters and stores nothing. A compliance workflow that trusts the response will believe data is locked when it is not. Silently ignoring a security-relevant parameter is worse than refusing it.
Resource footprint
Three rounds each, after the servers were ready and idle:
| Idle cgroup memory | VmRSS | Local image | Compressed | |
|---|---|---|---|---|
| Garage | 5.0 MiB | 27.1–27.5 MiB | 71.3 MB | 25.7 MB |
| Silo | 72.9 MiB | 131.5–136.8 MiB | 163 MB | 54.0 MB |
| RustFS | 55.5 MiB | 128.2–128.3 MiB | 299 MB | 109.3 MB |
Read the cgroup column as the billable number, and VmRSS as a diagnostic. VmRSS counts binary page mappings that the kernel can reclaim and that the container cgroup does not charge; a ranking built on VmRSS alone would make Silo and RustFS look four to five times heavier than they are. Neither column measures throughput — nothing here was benchmarked for IOPS, so do not infer a performance order from memory use.
Garage’s footprint is not a rounding difference: 5 MiB against 55–73 MiB means a $4 VPS can run it alongside other services, which is exactly the case it was designed for.
Startup
Time from container start to the first successful S3 call, three rounds:
| First successful S3 call | |
|---|---|
| Garage | 0.38–0.40 s |
| Silo | 0.60–0.84 s |
| RustFS | 0.45–0.46 s |
All three are fast enough that a healthcheck with a 2 s interval is realistic. RustFS answers 503 until it is ready, so a healthcheck that only checks the port will mark it healthy too early — poll /health instead.
Operational surface
- Garage has no web console. Anonymous requests to its S3 port return “does not support anonymous access yet”; administration is the admin API on
:3903plus thegarageCLI. It is a two-node-minimum design in production: the single-node test here says nothing about its replication behaviour. - Silo ships a console (
<title>SILO Console</title>on:9001) and a compatibility endpoint at/minio/health/livereturning 200 — the MinIO-shaped path, which matters if your monitoring already polls it. - RustFS answers
/health200 and serves its console under/rustfs/console/.
Migrating data
For Silo, the pitch is on-disk compatibility with MinIO’s metadata layout, which would mean pointing it at the existing data directory instead of copying objects. That was not tested here: no MinIO image could be pulled to seed a data directory, so this guide states the claim as documented by Silo’s own compatibility audit and nothing more. Treat it as the first thing to validate in a staging copy of your real data, not as a guarantee.
For Garage and RustFS, plan a copy: rclone or mc against both endpoints, then a checksum pass. At a few hundred GB this is a maintenance window; at multi-TB it is a project.
Two behaviours to expect from the client side, both observed:
- Garage rejects
ListBucketswhen the client signs forus-east-1:AuthorizationHeaderMalformed … expected: '20261019/garage/s3/aws4_request', while object-level PUT and GET succeed under any region. Set the region togaragefor bucket-level calls. - RustFS returns
503before it is ready, which a naive client treats as a hard failure rather than as a retry signal.
Our recommendation
Our recommendation for a homelab or a small self-hosted app Garage, if you use object storage as a dumb byte store: backups, media, static assets. 5 MiB idle and no console are features when the box also runs your database. Accept up front that versioning, tagging and bucket policies do not exist.
Our recommendation for a MinIO replacement that must not change behaviour Silo, if your app or your team depends on versioning, tags, public bucket policies and a console. It is the only one of the three that answers on the MinIO health path and ships a UI, and it is the only one with a documented on-disk migration story — which you must test before trusting.
Our recommendation if the license decides it RustFS (Apache-2.0) if you cannot ship AGPL-3.0 software in your product, or you want the permissive option with S3 semantics close to Silo’s. You pay for it in image size: 299 MB local against 163 MB.
Common problems
| Symptom | Product | Cause | Fix |
|---|---|---|---|
NotImplemented: PutBucketVersioning |
Garage | Feature does not exist | Keep versions in the app layer or choose Silo/RustFS |
Retention policy “set” but head_object shows none |
Garage | Object lock parameters accepted and dropped | Never trust the response; verify with head_object |
AuthorizationHeaderMalformed … expected 'garage/s3/aws4_request' |
Garage | Client signing for us-east-1 on bucket ops |
Use region_name="garage" |
| Anonymous GET returns 403 after “public” bucket | Garage | Bucket policies unimplemented | Serve through an app or a proxy |
| Healthcheck green but S3 calls fail | RustFS | 503 during init |
Poll /health, not the TCP port |
| SSE-S3 configured but objects unencrypted | Garage | Option silently ignored | Encrypt at the client or use Silo/RustFS with keys |
| No UI to inspect buckets | Garage | No console by design | Admin API on :3903 or the garage CLI |
| MinIO container will not pull | MinIO | Hub repo gone, Quay needs auth | Use another implementation or build from source |
What this guide does not claim
- No throughput, IOPS or concurrency benchmark was run. Nothing here ranks performance.
- Single node only. Garage’s replication, Silo’s and RustFS’s cluster modes were not exercised.
- The MinIO → Silo on-disk takeover was not tested — no MinIO image was obtainable to seed the data directory.
aws-cliandmcare not installed on the test host; every call went through boto3.