How to Run Your Own S3 Storage with MinIO (Buckets for Backups, Apps and Static Files)

S3 at home: MinIO. Buckets, access keys, lifecycle rules — and everything that speaks S3 points at your server instead of Amazon.

Half the software world now assumes an "S3 bucket" is where files go: backup tools, Immich and Nextcloud's external storage, static-site hosts, CI systems, your own apps. MinIO is an S3-compatible server in one container. Point those tools at https://s3.example.com with a key pair you made, and they behave exactly as they would with Amazon — on your disk, with no bill. It's also a good place to learn the S3 model before you ever pay for it.

Quick reference

TaskWhat
Folder~/docker/minio/compose.yaml
APIport 9000 (s3.example.com); console port 9001
First loginMINIO_ROOT_USER / MINIO_ROOT_PASSWORD
Bucket + keyConsole → Buckets → Create; Access Keys → Create
Command linemc alias set home https://s3.example.com KEY SECRET
Clientsrestic s3:, rclone s3 provider, Nextcloud external storage, Immich S3

1. The compose file

mkdir -p ~/docker/minio && cd ~/docker/minio
nano compose.yaml

services:
minio:
image: minio/minio:latest
container_name: minio
command: server /data --console-address ":9001"
ports:
- "127.0.0.1:9000:9000"
- "127.0.0.1:9001:9001"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
MINIO_BROWSER_REDIRECT_URL: https://minio.example.com
volumes:
- /mnt/data/minio:/data
restart: unless-stopped

.env with a root user and a long password (not minioadmin). Data on the big drive.

docker compose up -d

2. Reverse proxy

Two names (tutorial 28): s3.example.com → 127.0.0.1:9000 (the API, with client_max_body_size 0 and proxy_request_buffering off) and minio.example.com → 127.0.0.1:9001 (the console, with WebSocket lines). HTTPS on both. Internal-only is fine over the wildcard certificate (147).

3. Buckets and keys

Open https://minio.example.com, log in as root. Buckets → Create Bucket backups, then photos, site. Access Keys → Create access key: it gives an access key and secret — one pair per client, with a policy restricting it to its bucket:

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["s3:*"],
"Resource": ["arn:aws:s3:::backups", "arn:aws:s3:::backups/*"] } ] }

Least privilege (120): restic's key can't see the photos bucket.

4. The mc client

curl -LO https://dl.min.io/client/mc/release/linux-amd64/mc && chmod +x mc && sudo mv mc /usr/local/bin/
mc alias set home https://s3.example.com ACCESSKEY SECRETKEY
mc ls home
mc cp /mnt/backup/etc-2026-11-05.tar.gz home/backups/
mc mirror /var/www/mysite home/site

mc is cp, ls, mirror, rm for buckets, plus admin commands.

5. Point things at it

6. Lifecycle and versioning

Bucket → Lifecycle → Add rule: expire objects older than 90 days in backups, or move to a slower tier. Versioning on site keeps every overwritten version — undo for uploads. Object locking (set at bucket creation) makes a bucket WORM: append-only backups (116) in S3 form.

When plain folders are better

A Samba share or a bind mount is simpler for media the household browses, Docker volumes, and anything that doesn't speak S3. MinIO is for tools that do — backups, app storage, static hosting, S3 practice — and for giving each client its own key and bucket rather than a filesystem path.

Troubleshooting

"SignatureDoesNotMatch". Clock skew (timedatectl), or the proxy altered the Host header — proxy_set_header Host $host; must be present.

Uploads over 5 GB fail. The proxy's body size limit; multipart uploads need it unlimited.

Console works, API doesn't. s3.example.com not proxied to 9000, or the client is using path-style vs virtual-host style — MinIO accepts path-style by default (https://s3.example.com/bucket).

MinIO warns about a single drive. Correct — no erasure coding on one disk. It's a store, not redundancy; the drive underneath still needs the mirror/parity tutorials (97–99) and backups.

Wrapping up

One container, two proxied names, a bucket and a scoped key per client, mc for the command line. restic, rclone, Nextcloud, your site and your own code all talk to "S3" that lives on your disk — with lifecycle rules, versioning and object locking when you want them.

Call us