Minio: S3-Compatible Object Storage in Your Cluster

Your own S3 endpoint, on hardware you control

Half the software I self-host now expects an S3 bucket to throw things into. Backup tools, container registries, CI artifact stores, Loki for logs, photo libraries — they’ve all standardised on the S3 API as the lingua franca of “somewhere to put blobs”. You can hand all of them an AWS account and a credit card, or you can run Minio and have a real S3-compatible endpoint inside your own cluster, on your own disks, with no egress bills and no surprise invoices. I’ve run it for years and it has quietly become load-bearing infrastructure.

Advertisement

Minio is a single Go binary that speaks the Amazon S3 API. Point any S3 client at it — the AWS CLI, rclone, an SDK, an app’s built-in S3 backend — and it works, because the API surface really is compatible. It does buckets, objects, presigned URLs, multipart uploads, bucket policies, and IAM-style access keys. For most applications that “support S3”, Minio is a drop-in target and they cannot tell the difference.

It is not a distributed filesystem and not a POSIX mount. Objects are objects: write the whole thing, read the whole thing, no partial in-place edits. That’s the S3 model, and if you fight it you’ll have a bad time. Used as intended — a place for immutable blobs — it’s rock solid.

You do not need a cluster to begin. A single container with one volume gets you a working endpoint and a web console:

services:
  minio:
    image: quay.io/minio/minio:latest
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: admin
      MINIO_ROOT_PASSWORD: "change-me-please-12345"
    ports:
      - "9000:9000"   # S3 API
      - "9001:9001"   # web console
    volumes:
      - minio-data:/data
    restart: unless-stopped

volumes:
  minio-data:

Port 9000 is the S3 API; 9001 is the browser console where you create buckets and access keys by hand. Once it’s up, the official mc client is the fast way to drive it from a terminal:

# register an alias for your server
mc alias set home http://localhost:9000 admin change-me-please-12345

# create a bucket and copy a file in
mc mb home/backups
mc cp ./db-dump.sql.gz home/backups/

# generate a scoped access key for an app, not root creds
mc admin user svcacct add home admin

That last command matters: never hand an application your root credentials. Create a service account scoped to the buckets it needs, exactly as you would with real IAM.

The single-node toy above keeps your data on one disk with no redundancy. For anything you care about, Minio’s actual strength is erasure coding — it splits objects into data and parity shards across multiple drives so it can survive disk and node failures without RAID underneath. You need a minimum set of drives (it likes counts that divide evenly), and you give it all of them at once:

# distributed mode across 4 nodes, 4 drives each, expressed as an erasure set
minio server http://minio{1...4}/data{1...4} --console-address ":9001"

That brace-expansion syntax is Minio-native: it expands to sixteen drives across four hosts, and Minio computes the parity layout so you can lose drives — up to half, depending on the configured parity — and still read every object. This is the configuration I actually run, because the whole point of self-hosting storage is not waking up to a single dead disk having eaten your backups.

In a cluster I deploy Minio as a StatefulSet so each pod gets stable identity and its own PersistentVolumeClaim. The official Helm chart handles the wiring, but the shape is simple: N replicas, one PVC each, a headless service for the peers to find each other, and a regular service for clients. The thing to get right is that the PVCs must be backed by local or block storage you trust — putting Minio’s data on a network filesystem that’s itself doing replication is doubling your overhead and confusing the failure model. Let Minio own the redundancy.

One operational note that has burned people: do not arbitrarily resize your erasure set after the fact, and don’t mix wildly different drive sizes. Minio sizes its parity layout at startup, and bolting on mismatched capacity later is not the seamless expand-a-RAID experience you might expect. Plan the layout, then grow by adding whole new server pools rather than fiddling with the existing set.

If you self-host anything that wants an S3 bucket — and increasingly everything does — yes, emphatically. One small Minio instance turns “this app needs AWS” into “this app needs a URL and an access key I generate locally”, which is a huge reduction in cloud dependency, cost, and data-residency hand-wringing. The single-node Docker setup takes ten minutes and immediately makes a dozen tools happier.

The caveats are real, though. Run it single-node and it’s a single point of failure; you must go erasure-coded and multi-drive before trusting it with data you can’t lose. And remember it’s object storage, not a filesystem — don’t try to mount it as a home directory. For backups, artifacts, logs, registry layers, and anything that fits the write-once-read-many blob pattern, Minio has been one of the best returns on a weekend’s setup in my whole stack.

Advertisement

Related Content

Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.