Modern AI and data workloads live and die by storage. Training corpora, checkpoints, dataset shards, model weights, Parquet lakes, backup archives — all of it eventually lands in object storage because that is the one abstraction every tool agrees on. The S3 API became the lingua franca: Spark reads from it, MLflow stores artifacts in it, and Kubernetes operators mount it for everything from logs to datasets. When you need that API on your own hardware — for cost, for data locality, for compliance — MinIO is the open source answer that has defined the category for a decade.
MinIO is written in Go, ships as a single binary, and speaks the S3 API natively rather than emulating it through a translation layer. Point any S3 SDK or the mc client at a MinIO endpoint and it just works. But the genuinely interesting part is underneath the API surface: MinIO takes a pile of ordinary disks (or a fleet of ordinary servers) and turns them into a single highly available namespace that survives disk failures, host failures, and even the silent bit-level rot that plagues long-lived drives. It does this with Reed-Solomon erasure coding, a custom on-disk metadata format, and a quorum-based distributed lock service — all implemented from scratch in the repo we are walking through today.
This post is a source tour of minio/minio at its default branch (master, commit 7aac2a2c5b7c). We traced the request path from the HTTP edge down to the raw disk driver, verified every symbol and line number, and drew two architecture maps. The overview shows the big layers; the detail map goes one level deeper into erasure mechanics, locking, and cluster messaging.
Overview: from the S3 router at the edge, through the ObjectLayer contract and the pool/set topology, down into the Reed-Solomon engine, the per-disk XL storage layer, and the namespace lock service.
Reading the overview from left to right:
- Everything starts at registerAPIRouter, L255, where every S3 endpoint is mounted on a mux router, guarded by s3APIMiddleware, L210. The
objectAPIHandlersstruct that carries all handler state is defined right there at L68. - The write path funnels into PutObjectHandler, L1793, which authenticates, parses headers, streams the body, and calls the backend; reads come back through GetObjectHandler, L717 and HeadObjectHandler, L1012.
- Both handlers speak to the backend only through the ObjectLayer interface, L246 — a clean seam that lets the same HTTP layer serve single-node and clustered deployments.
- At boot, serverMain, L746 validates the endpoint list and calls newObjectLayer, L1199, which returns a running backend wired into the global server state.
- The backend is a stack: erasureServerPools, L52 federates one or more pools, newErasureSets, L352 carves each pool into sets of disks, and the hash placement function hashOrder, L178 decides which disk in a set owns which shard.
- Object operations execute inside a set: PutObject, L1249 drives encoding and parallel shard writes, while GetObjectNInfo, L203 reassembles data even when some disks are offline.
- Finally, every shard lands on xlStorage, L98, the per-disk driver whose WriteAll, L2305 and CreateFile, L2092 methods are where bytes actually meet the filesystem, with object metadata persisted in the
xlMetaV2format at L901.
The detail map below adds the machinery the overview abstracts away: multipart upload state, healing, bitrot checksums, the distributed lock stack, peer-to-peer RPC, and the configuration/crypto subsystems.
Detail view: handlers, the object layer, the erasure engine, XL storage, quorum locking via DRWMutex and dsync, grid-based peer messaging, and the config/KMS layer.
From Install to Your First Object
MinIO’s README is refreshingly blunt about its distribution model these days: the community edition ships as source code only, and the recommended install is a one-liner:
go install github.com/minio/minio@latest
You need Go 1.24 or newer. Once the binary exists, starting a server is exactly what you would hope: minio server PATH, where PATH is any empty folder. On first boot the server comes up with default root credentials minioadmin:minioadmin, and the embedded web console is available at http://127.0.0.1:9000 — from there you can create buckets and upload objects with a browser. That single-folder mode is not a toy: it is the same code path — the same xlStorage driver and erasure machinery, just with one disk — that a fifty-node cluster runs.
To exercise the S3 API properly, grab the MinIO Client (mc) and run:
mc alias set local http://localhost:9000 minioadmin minioadmin
mc admin info local
mc mb local/data
mc cp ~/Downloads/mydata local/data/
That is the full loop: alias the endpoint, inspect the deployment, create a bucket, and copy real files in. Everything mc does here translates to ordinary S3 REST calls hitting the handlers in the diagram above — mc mb ends up at PutBucketHandler, L720, and mc cp ends up at PutObjectHandler. For container users, the README documents building a local image with docker build . and running it as docker run -p 9000:9000 -p 9001:9001 myminio:minio server /tmp/minio --console-address :9001, exposing both the S3 API and the console port. For anything beyond a laptop, the same binary expands horizontally: list multiple hosts and disks on the command line, and MinIO forms pools and sets across them — no separate “coordinator” process to install. Teams that run Kubernetes can pair it with the official MinIO Operator, and it slots naturally next to other self-hosted infrastructure we have covered, from Kubernetes itself to a Hugging Face Hub mirror for model artifacts.
How Erasure Coding Actually Works
The heart of MinIO is not a database or a journal — it is a Reed-Solomon codec. The Erasure struct, L35 holds a data-shard count, a parity-shard count, and a block size, and NewErasure, L42 sanity-checks the parameters (at most 256 shards) before lazily constructing a klauspost/reedsolomon encoder. On a write, Erasure.Encode, L69 streams the object in blocks, splitting each block into data shards and computing parity shards, then fans the shards out to per-disk writers in parallel. Because the code is systematic, data shards are directly readable and parity shards are pure insurance: with 8 data and 4 parity shards per set, any 8 of the 12 disks can reconstruct the object.
Placement is deterministic rather than replicated. hashOrder, L178 hashes the object name into a shuffle of disk indexes, and newFileInfo, L388 records that distribution in the object’s metadata, so every process in the cluster computes the same answer for “where does shard 3 live”. Reads go through Erasure.Decode, L239, which wraps multiple disk readers in a parallelReader and pulls from whichever disks respond fastest, tolerating absent or corrupt shards as long as the read quorum holds.
Two quieter pieces make this production-grade. First, bitrot protection: bitrot-streaming.go wraps every shard in a streaming hash, so silent corruption on any drive is detected at read time instead of being served to users. Second, healing: HealObject, L1060 notices stale or missing shards, reads the surviving ones, re-encodes through Erasure.Heal, L317, and rewrites the damaged disks in place. Together these give MinIO the durability story that a naive “replicate three times” design struggles to match at the same disk cost.
The Storage Layer Beneath
Below the erasure engine sits the XL storage layer, one xlStorage instance per disk. Its methods are deliberately boring — ReadAll, L1829, WriteAll, L2305, CreateFile, L2092, RenameFile, L2997 — because all the intelligence lives above it. Each object is stored as its shard files plus a metadata blob named xl.meta, formatted by xlMetaV2, L901. That format is version-aware: a single xl.meta can carry multiple object versions, per-part information for multipart uploads, checksum records, and inline data for small objects, which lets tiny objects be served without extra file reads.
Metadata correctness is enforced by quorum, not by a leader. erasure-metadata.go implements functions like findFileInfoInQuorum, L289 that gather FileInfo answers from all disks in a set and accept only what a majority agrees on, with helpers like ObjectToPartOffset, L268 translating byte ranges into shard reads. Disk health is probed through calls like CheckParts, L2399. This metadata-is-data philosophy — the same erasure machinery protects object bytes and metadata alike — keeps the system self-describing after any crash.
Locks, Peers, and Cluster Consistency
Object storage looks embarrassingly parallel until two clients race on the same key. MinIO serializes this with a three-tier lock stack. Locally, namespace-lock.go builds an in-process nsLockMap (via newNSLock, L66) that merges concurrent lockers for the same resource. Cluster-wide, the DRWMutex type at L113 of internal/dsync/drwmutex.go implements distributed read/write locks that require a quorum of N/2+1 grants — Lock, L158 blocks until enough remote lockers say yes, and a refresh loop keeps the grant alive during long operations. The quorum negotiation itself lives in internal/dsync, against the NetLocker interface, L23.
Those lockers are wired to real endpoints by newLockAPI, L101, one stub per peer host, and served on the far side by LockHandler, L52. Note what is not here: no ZooKeeper, no etcd, no external consensus service. All intra-cluster messaging — locks, peer REST calls, notifications between nodes — rides on internal/grid, a connection manager that multiplexes typed RPC streams between nodes. The client side of that mesh is peer-rest-client.go (its grid wiring is visible around L49), and the server side is peer-rest-server.go. It is a textbook example of a Go project betting on simple, self-contained primitives.
Where MinIO Fits in an AI Stack
For ML work, MinIO is usually the quiet dependency underneath everything else. It backs artifact stores — point MLflow at an S3 endpoint and every run’s models and metrics land in MinIO buckets. It holds datasets, index snapshots, and analytic extracts that feed engines like ClickHouse, and it gives long-running training jobs a durable place to checkpoint. The design choices we traced above are exactly why: the S3 API means no bespoke client code, versioning means experiments are reproducible, and erasure coding means a disk loss during a week-long training run is an operations ticket rather than a research incident.
Honest Limits
The most important caveat is the one MinIO states itself: as of the current README, the community edition is distributed as source code only, with no maintained pre-compiled binary releases — legacy binaries exist but “will not receive updates”. If you do not maintain a Go toolchain, the Docker-build path becomes the practical route, and production use of self-compiled binaries is explicitly at your own risk under AGPLv3, which also has licensing implications for some commercial embedded uses. Second, this is real distributed systems software: the quorum locking and pool/set topology demand that you think about server counts, drive counts, and network reliability up front; a two-node “cluster” with one dead node is not degraded, it is read-only, by quorum design. Third, capacity planning matters: objects in a set are striped across that set’s disks, so unbalanced sets or pools of very different ages will show uneven utilization — the pool-routing code picks where new objects land, but it cannot rewrite history. Finally, while MinIO is S3-compatible in the ways almost everyone uses, it is not a bit-for-bit S3 clone; exotic IAM corner cases and some newer AWS-only features may behave differently, so test the SDK paths your application actually exercises. None of these are hidden, but they belong in any evaluation.
{“@context”:”https://schema.org”,”@type”:”TechArticle”,”headline”:”MinIO Under the Hood: How a Go Object Store Turns Disks into an S3 Cloud”,”description”:”A guided source tour of MinIO: S3 handlers, erasure sets, the XL storage layer, and quorum-based distributed locking in Go.”,”datePublished”:”2026-10-31”,”author”:{“@type”:”Person”,”name”:”PyShine”},”publisher”:{“@type”:”Organization”,”name”:”PyShine”},”keywords”:”minio, object storage, s3 compatible, erasure coding, go”,”codeRepository”:”https://github.com/minio/minio”,”programmingLanguage”:”Go”} Enjoyed this post? Never miss out on future posts by following us