MinIO at Home: Your Own S3 Without the Bill
Run an S3-compatible object store on hardware you already own

Contents
Half the interesting software I want to run at home assumes it can talk to S3. Backup tools want an S3 bucket. Databases want to stream write-ahead logs to one. Static-site tooling, container registries, log shippers, photo apps — an enormous amount of modern software speaks the S3 API as a first-class storage backend, and treats a local disk as the awkward special case. For years that meant either paying a cloud provider per gigabyte and per request, or giving up the feature. MinIO removes the dilemma: it is a single Go binary that presents the S3 API over your own disks, so everything that expects S3 gets S3, running on a mini PC in the cupboard for the price of the electricity.
This is a practical guide to running MinIO at home — why you would want an S3 endpoint on your own hardware, how to stand one up with docker-compose, how to drive it with the mc client, and the specific decisions (single drive versus erasure coding, versioning, object locking) that matter once real data lands in it. I run mine mostly as a backup target, and it pairs so naturally with client-side backup tools that half of this article is really about that.
Why bother when a NAS already stores files
A NAS gives you files over SMB or NFS. That is a filesystem, and plenty of software is happy with a filesystem. The S3 API gives you something different: a flat namespace of objects in buckets, addressed over HTTP, with a well-defined set of operations for uploading, versioning, listing, setting retention and generating time-limited access links. Software written for the cloud targets that API, and getting the same API at home unlocks all of it without code changes.
Three capabilities in particular are hard to get from a plain file share and easy to get from S3. Versioning keeps every historical version of an object, so an overwrite or delete does not destroy the old copy. Object locking (write-once-read-many, WORM) lets you mark objects immutable until a date, which is the single most effective defence a backup target has against ransomware, because even a compromised client with valid credentials cannot delete a locked object. And presigned URLs let you hand out a link that grants temporary access to one object without sharing any credentials. Those three are why an S3 endpoint at home is worth more than the SMB share you already have.
Standing it up with docker-compose
MinIO ships as a container and needs almost nothing to run. Point it at a data directory, give it a root user and password, expose the API port and the console port, and you are done. Here is a minimal single-node setup:
| |
docker compose up -d and MinIO is live: the S3 API on port 9000, a web console on 9001. The MINIO_SERVER_URL tells MinIO the public address it lives behind, which matters once you put it behind a reverse proxy with TLS — presigned URLs and console redirects use it. Do not skip a real TLS certificate for anything you will point backup tools at; several S3 clients refuse plaintext by default, and you do not want your object storage credentials crossing even a home network in the clear.
The root user is an all-powerful admin. Once things work, create scoped users with a policy limited to the one bucket a given application needs, and hand those keys to your apps. The root credentials should live in a password manager and be used only for administration.
Driving it with mc
MinIO’s command-line client, mc, is how you do everything the console can do and a lot it cannot. First register an alias pointing at your server:
| |
For the classic homelab use — mirroring a folder into a bucket — mc mirror behaves like rsync for object storage:
| |
mc also handles lifecycle rules, replication between MinIO instances, event notifications and admin tasks like watching the server’s real-time trace. It is the tool you will actually live in, so it is worth ten minutes learning its verbs.
Single drive or erasure coding
MinIO runs in two broad shapes at home, and picking the wrong one leads to disappointment later.
Single-node single-drive is the server /data example above: one process, one directory, no redundancy inside MinIO itself. This is the right choice when MinIO sits on top of storage that is already redundant — a ZFS mirror, a RAID array, an LVM volume with parity. MinIO stores plain objects on that filesystem and leaves the redundancy to the layer beneath it. This is my setup, because my pool already handles disk failure and I would rather not have two redundancy schemes fighting each other.
Erasure coding is MinIO providing its own redundancy across multiple drives (or nodes). You give it several disks and it stripes data plus parity shards across them, so it can lose drives and reconstruct. This is the right choice when you are handing MinIO raw, non-redundant disks and want it to be the redundancy layer. It needs a minimum number of drives to be useful and the maths on how many failures it survives depends on the erasure set size. Do not run erasure coding on top of an already-redundant pool — you would be paying for parity twice.
If you already have a ZFS or RAID pool you trust, deciding how that pool should be laid out in the first place is the more consequential decision, and when mirrors beat parity in a homelab covers the trade-offs that MinIO then simply inherits.
The killer use: an S3 backup target you control
The reason MinIO earns its keep on my network is that it turns “back up to S3” from a monthly cloud bill into a one-off hardware cost. Restic, Kopia, database WAL shippers and countless other tools can write straight to an S3 endpoint, and they do not care whether that endpoint is Amazon or a box in your cupboard. Point them at your MinIO and the client-side encryption those tools do means MinIO only ever stores ciphertext.
This is exactly how I run my local backup leg. A tool like Restic encrypts everything before it leaves the machine and then uploads to the bucket, so the design mirrors the approach in offsite backups that survive your own mistakes — the storage server is a dumb, untrusted vault of opaque blobs. Databases are the other big consumer: streaming SQLite replication to object storage, as in Litestream to an object store, gives you continuous point-in-time database backups landing in a MinIO bucket seconds after each write.
Enable object locking on the backup bucket and you get the ransomware defence for free. Create the bucket with locking turned on and set a retention window:
| |
Now every object written to home/vault is immutable for thirty days. A compromised client with full credentials can upload new objects but cannot delete or overwrite the locked ones, so your backup history survives an attacker who owns the machine being backed up. Combine that with MinIO as one storage location in a broader 3-2-1 plan and the local copy is both cheap and hard to destroy.
Troubleshooting: what bites people first
mc reports SignatureDoesNotMatch or a 403. Almost always a clock or credential problem. S3 signatures are time-sensitive, so a server whose clock has drifted more than fifteen minutes rejects every request — check NTP is running on the MinIO host. If the clock is fine, the keys or the region are wrong; re-run mc alias set with the exact credentials.
A client refuses to connect over HTTP. Many S3 libraries default to HTTPS and will not downgrade. Put MinIO behind a reverse proxy with a real certificate and set MINIO_SERVER_URL to the HTTPS address. Self-signed certificates work only if you add the CA to the client’s trust store, so a proper certificate saves pain.
Presigned URLs point at the wrong hostname. If a generated link contains an internal container name or localhost, MINIO_SERVER_URL is unset or wrong. MinIO builds those URLs from that variable, so set it to the address clients actually reach.
Uploads fail with “disk not writable” or MinIO refuses to start. MinIO is strict about the data directory. It wants a dedicated directory it owns, on a real filesystem, and it dislikes being pointed at the root of a mount or a directory with pre-existing unrelated files. Give it an empty subdirectory (/srv/minio/data), owned by the container’s user, and let it manage the layout inside.
You changed the erasure-set drive count and everything broke. MinIO’s drive topology is fixed at initialisation. You cannot add a single disk to an erasure set or reshape it in place the way you might grow a filesystem; expansion happens by adding whole new server pools. Plan the drive count before you write data, because changing it afterwards means migrating everything out and back.
The console loads fine but S3 clients get a certificate error. MinIO’s console and its S3 API sit behind the same TLS certificate. A certificate valid for the console hostname but not for the S3 hostname (or an IP-only certificate) makes strict S3 clients reject the API while the more forgiving browser still opens the console. Issue the certificate for the exact hostname your clients use in MINIO_SERVER_URL, and include every alias they might connect through.
Versioning filled the disk. With versioning on, every overwrite and delete keeps the old version forever unless a lifecycle rule expires them. Set an expiry lifecycle (mc ilm add) so non-current versions are cleaned up after a sensible window, or the “keep everything” default will quietly eat the pool.
Verdict: is it worth it, and for whom?
If you run anything that speaks S3 — and a surprising amount of self-hosted software does — MinIO is close to essential, and it is genuinely easy to stand up. As a backup target it pays for itself the first month you would otherwise have sent to a cloud provider, and object locking gives you a ransomware defence that a plain file share cannot. Set it on top of storage that is already redundant, use a proper TLS certificate, scope your application credentials down from root, and it will run for years as quiet infrastructure.
It is overkill if all you want is to sync a folder between two machines — Syncthing or plain rsync is simpler and you will never touch the S3 features. And it demands a little respect: the drive topology is fixed at init, versioning needs lifecycle rules to stay bounded, and TLS is close to mandatory for real use. For a homelabber who wants cloud-shaped storage on hardware they own, running the exact API the whole ecosystem targets, it is one of the highest-leverage services you can host. Mine has been up long enough that I forget it is there, which is the highest compliment I give a piece of infrastructure.



