S3 object storage after MinIO
In late 2025 the company behind MinIO wound down its community edition — web console cut to a stub, prebuilt community binaries stopped, repository archived. That stranded two distinct populations: demos/compose files that used MinIO as a local S3 stand-in, and self-hosted production deployments. The replacements are not the same for each — the candidates divide sharply on concurrency correctness (conditional PUT, atomic updates, ETag semantics) and crash consistency, which is exactly what table formats like Apache Iceberg require.
Grounded in: rmoff’s single-node comparison (2026-01-14, versions tested then), the HN discussion it triggered (2026-09-16), and source-code verification of each repo at the tagged release (2026-09-16; details per project below).
Why conditional writes decide this field
Iceberg (and Delta without an external lock provider) commit by atomically swapping a pointer — PutObject with If-None-Match: * (create-only) or If-Match: <etag> (compare-and-swap on the current pointer). If the store returns success for a losing concurrent writer, two writers both believe they committed and the table silently diverges. AWS shipped PutObject conditional headers in August 2024; MinIO had them since February 2023.
A store that is only eventually consistent cannot offer this guarantee at all — and no per-project feature matrix will tell you; you have to read the write path.
The field
| Project | Multi-node | Conditional PUT (If-Match/If-None-Match) | Crash consistency | Homelab fit | Production fit |
|---|---|---|---|---|---|
| silo (OSS) (MinIO fork) | ✅ erasure-coded pools | ✅ inherited from MinIO (checkPreconditionsPUT) | ✅ MinIO’s battle-tested write path | ✅ drop-in | ✅ known quantity |
| rustfs (OSS) | ✅ distributed EC | ✅ server-side precondition checks + race-hardening commits | ⚠️ young; HIGH CVE-2026-55189 (fixed) | ✅ single binary | ⚠️ GA since 2026-09-16 |
| seaweedfs (OSS) | ✅ filer+volume servers | ✅ full impl in 4.47 (WriteCondition under filer lock) + conditional DELETE | ⚠️ fsync per-bucket opt-in; index fully in RAM | ✅ | ⚠️ memory ∝ object count |
| hs5 (OSS) | ❌ single-node by design | ✅ checked before upload, on by default | ✅ fsync before success response (strong) | ✅ simplest | ❌ no replication |
| garage (OSS) | ✅ geo-distributed CRDT | ❌ maintainer-confirmed impossible without rearchitecture | ⚠️ fsync off by default; LMDB default can corrupt on hard shutdown | ⚠️ | ❌ for Iceberg |
Concurrency and consistency, per project
silo — the MinIO continuation
A fork maintained by Vonng (PGSTY) since 2026-03-02, renamed from pgsty/minio on 2026-08-06, 3.1k stars, releases every 1–2 months (latest: 2026-09-16). It inherits MinIO’s full conditional-write implementation (checkPreconditionsPUT on the PUT path, If-Match/If-None-Match → 412 Precondition Failed) and its erasure-coded multi-node server pools. AGPL-3.0, no CLA, telemetry removed. For existing MinIO deployments this is the no-code-change path — the fork explicitly exists to keep them running (“the fork is a means, not an identity”).
Fit: drop-in for both homelab and production. The main question is bus-factor — one principal maintainer with a strong public commitment record (manifesto: public advisory per fix, tested rollback per release).
rustfs — Swift + Keystone, GA 2026-09-16
The differentiator for us: native OpenStack Swift API with Keystone authentication (dedicated keystone crate, protocols/src/swift — integration-tested). For a COT/Homelab environment that runs Swift at work, this makes rustfs a dev/simulator for Swift and Keystone, not just S3-compatible storage.
Server-side conditional writes are implemented (storage-api/src/object.rs precondition checks), and the team has been actively hardening them — rc.4 “fail incomplete conditional PUT races”, rc.5 “preserve atomic 1 MiB conditional writes”. Version 1.0.0 GA released 2026-09-16. Apache 2.0 (the AGPL-free license is a stated selling point). Distributed erasure coding, MinIO-like architecture.
The GA announcement (2026-09-16) adds two more edges:
- S3 Tables — an Apache Iceberg REST Catalog built into the storage kernel. rustfs doesn’t just back Iceberg-with-conditional-PUT like the others; it ships the catalog itself (AWS S3 Tables-style), so DuckDB/Spark/Trino point at the rustfs REST endpoint instead of standing up a separate catalog (Polaris/Nessie/REST-catalog container). Deeper Iceberg investment than any other candidate here — SeaweedFS has Iceberg maintenance tasks (compaction), not a catalog.
- Adoption and pace: first code Feb 2024 → open-sourced Jul 2025 → GA in 2y7m; vendor-claimed 32k+ stars, 10M+ Docker pulls, 2.7M+ deployed instances, 160+ contributors, NVIDIA Inception. Protocol surface beyond S3/Swift: WebDAV, FTP/FTPS, MCP. (Adoption numbers are the vendor’s own GA post — mechanics verified in-repo, scale claims unverifiable.)
Caveats: young security track record — CVE-2026-55189 (HIGH 7.7: any authenticated FTP-listener user could read any object, fixed in beta.12) plus a 2026-07 medium. The FTP surface is turned off by default but it’s a reminder the protocol surface is broad; GA-day-one production claims deserve observation, not automatic trust.
Fit: strong homelab candidate (Swift simulation + S3 + built-in Iceberg catalog + single Rust binary). Production: the GA claim plus adoption is credible for greenfield, but with GA only since 2026-09-16 and a short CVE history, silo remains the lower-risk MinIO replacement.
SeaweedFS — conditional writes shipped, mind the metadata
Oldest project here (2014, single primary maintainer chrislusf, 34k stars, Apache 2.0). The conditional-write story the 2024 GitHub discussion said was missing is done: s3api_object_routed_write.go reduces If-Match/If-None-Match to filer WriteCondition clauses evaluated under the filer lock (routed write for the reducible cases, lock-path evaluation otherwise), plus conditional DELETE with If-Match. Verified present in release 4.47 (2026-09-14). It now even ships native Iceberg table support (weed/worker/tasks/iceberg/) and advertises “billions of files” via O(1) disk access.
The trade-offs are operational: the [object → disk] index lives entirely in memory (startup scan + RAM ∝ object count — hs5’s author flags this as unsuited to fast-restart/testing scenarios), and durability needs per-bucket fsync enabled (Path-Specific Configuration wiki) — without it a crash can lose acknowledged writes.
Fit: good homelab fit for large-file/large-count storage with fsync on. Production is real (many run it), but memory-bound metadata and single-maintainer governance deserve weighing.
hs5 — strongest single-node semantics, deliberately no cluster
C++ (folly/proxygen), LGPL-3.0+, 193 stars, by the UrBackup author. Single-node by design (“scale-up: run it on a better machine”). Two properties stand out:
- Durability by default: data and metadata are fsynced before the success response returns — power-cut after a 200 means the object is there. AWS-grade read-after-write consistency, no eventual consistency.
- Conditional PUT checked pre-upload (
check_conditional_headers_before_upload, default true) — If-Match/If-None-Match evaluated before the body is read.
Storage is one LMDB index + append-only data0 file (index on SSD, data on HDD works). Missing: POST Object upload, object locks. A 10k-small-file benchmark in-repo puts hs5 ~11s vs rustfs ~13s vs minio ~14.4s vs garage ~16.5s (fsync-compensated).
Fit: the simplest correct local S3 for demos, CI, and single-box homelab use — one binary, two files, no moving parts. Not for production data you can’t lose — one node, no replication.
Garage — excellent geo-storage, wrong tool for Iceberg
The confirmation of the opening concern: maintainer reply on issue #1052 (2025-05-29) — conditional writes are “not possible with our weak-consistency replication model, it would require a full rearchitecting to use a strong consensus algorithm, which would make garage much slower.” Garage is CRDT-based, geo-distributed-first, AGPL, grant-funded (NLnet). Strong read-after-write is provided via quorum reads, but CAS-style atomic writes are architecturally out.
Crash consistency also needs manual care: metadata_fsync/data_fsync are off by default; hs5’s README documents that default-config LMDB “actually runs in a mode where data gets corrupted on hard shutdown” (they recommend LMDB only with replication ≥ 2), and even SQLite metadata wants synchronous = FULL rather than the default NORMAL. v2.3.0 added garage server --single-node --default-bucket for easy single-node setups, but easy setup ≠ safe single-node defaults.
Fit: great for multi-site self-hosted blob storage (its actual design goal). Not for Iceberg/table-format workloads; single-node defaults are a durability trap.
Recommendations (as of 2026-09-16)
- Replacing MinIO anywhere (homelab or prod): silo — identical semantics, conditional writes inherited, active CVE-patched releases.
- Dev/simulation of Swift + Keystone + S3 at work: rustfs — the only one with a native Swift/Keystone API, GA as of 2026-09-16; keep an eye on its CVE record.
- Iceberg without extra moving parts: rustfs again — built-in S3 Tables Iceberg REST catalog means one fewer container vs standing up Polaris/Nessie next to any of the others.
- Local S3 for demos/CI/homelab with the fewest moving parts: hs5 — strongest crash-consistency story of the lot, but single-node only.
- Large-scale homelab storage with billions of small files: SeaweedFS with per-bucket fsync on; conditional writes now shipped.
- Geo-distributed cheap-and-cheerful blobs: Garage — but never for Iceberg, and turn fsync on.
Fewer moving parts ranking (simplest → most): hs5 (1 binary, 2 files) → silo (1 binary, erasure sets) → rustfs (1 binary, distributed EC) → SeaweedFS (master+volume+filer roles) → Garage (cluster layout).
Sources
- rmoff, Alternatives to MinIO for single-node local S3 (2026-01-14) — single-node bake-off; versions tested are 8 months old
- HN discussion of the above (2026-09-16) — silo adoption reports, Garage v2.3.0 single-node flag, RustFS CVE sentiment, Versity GW mention
- Garage issue #1052 (git.deuxfleurs.fr, 2025-05) — maintainer statement on conditional writes
- SeaweedFS discussion #5299 (2024) +
weed/s3api/s3api_object_routed_write.goat 4.47 — conditional-write implementation - MinIO blog: conditional writes merged Feb 2023; AWS S3 conditional writes (Aug 2024)
- Repo source verified 2026-09-16 at: silo RELEASE.2026-09-16, rustfs 1.0.0, seaweedfs 4.47, hs5 main (Aug 2026)