Three months after setting up my HomeLab, my AWS S3 bill crept past $40/month. Not because I was doing anything unusual — just storing VM snapshots, Grafana dashboard exports, and the occasional Docker volume backup. That number kept climbing every time I added a new service.
If that sounds familiar, MinIO is the answer. It’s an S3-compatible object storage server you can run on your own hardware, and after running it in my HomeLab for over a year, I’ve never looked back.
The Real Problem: Cloud Storage Creep
Most people underestimate how fast object storage costs accumulate in a HomeLab context. AWS S3 isn’t expensive for a few gigabytes, but the moment you start automating backups — daily snapshots of databases, Docker volumes, config files — you’re dealing with hundreds of gigabytes without noticing.
Beyond cost, there’s the latency problem. If your backup process writes to S3 in us-east-1 and your HomeLab is in Tokyo, you’re adding 150ms+ per request to every backup job. For large files, this turns a 10-minute backup window into 45 minutes.
The deeper issue: you’re building dependency on a third-party service for data that never leaves your local network. Restic, Duplicati, Nextcloud, and half a dozen other self-hosted tools all support S3 — which means they can all point at your local MinIO instance instead.
Why Cloud S3 Isn’t Always the Right Tool
AWS S3 is genuinely excellent for production workloads at scale. But HomeLab usage doesn’t match that profile:
- Data rarely leaves your local network, so egress costs are friction, not value
- You don’t need 11 nines of durability for a Jellyfin media cache or a Grafana backup
- API rate limits on free tiers break automated backup schedulers
- Privacy: some data you simply don’t want sitting on a third-party cloud provider
The root cause is a mismatch between cloud storage’s pricing model (pay per GB + per request + per transfer) and HomeLab usage patterns (bursty writes, local reads, flexible retention).
Self-Hosted Object Storage: What Are Your Options?
Before committing to MinIO, I evaluated three alternatives:
Ceph
The enterprise choice. Ceph handles object, block, and file storage in a distributed setup. It’s production-grade but wildly over-engineered for a single-node HomeLab. Minimum viable deployment requires 3 nodes and significant RAM overhead. Skip it unless you’re running a multi-server rack.
Garage
A lightweight, Rust-based alternative designed specifically for self-hosted use cases. Very small memory footprint, runs fine on a Raspberry Pi. The ecosystem support is still catching up though — some S3 clients need workarounds to function correctly with Garage’s API implementation.
MinIO
The sweet spot for HomeLab. S3-compatible API means any tool that supports S3 just works — zero configuration changes on the client side. Runs efficiently on a single node. Has a clean web UI. Active development with solid documentation. The community edition is fully open source under AGPL.
I’ve applied MinIO in production for both client projects and my own HomeLab, and the results have been consistently stable. It handles everything from Restic backup targets to Nextcloud external storage without any issues.
Setting Up MinIO on Docker
Here’s the setup I actually use. Create a docker-compose.yml in a dedicated directory:
version: '3.8'
services:
minio:
image: quay.io/minio/minio:latest
container_name: minio
restart: unless-stopped
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: admin
MINIO_ROOT_PASSWORD: your_strong_password_here
MINIO_VOLUMES: /data
volumes:
- /opt/minio/data:/data
ports:
- "9000:9000" # S3 API
- "9001:9001" # Web console
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 10s
retries: 3
A few things worth getting right from the start:
- Use a proper path for
/opt/minio/data— ideally a dedicated disk or ZFS dataset, not your OS drive - Set a strong root password immediately; MinIO’s API is accessible over the network, and default credentials are the #1 security mistake in HomeLab setups
--console-address ":9001"separates the API port (9000) from the web UI port (9001) — important when you’re putting Nginx or Traefik in front
Start it and verify:
docker compose up -d
docker compose logs -f minio
Access the console at http://your-server-ip:9001 and create your first bucket.
Configure Service Accounts, Not Root Credentials
Never use your root credentials in application configs. MinIO has proper IAM support — create a service account for each application that needs access.
From the MinIO console: Identity → Service Accounts → Create
Or via the MinIO client (mc):
# Install mc
curl -O https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
sudo mv mc /usr/local/bin/
# Configure alias
mc alias set local http://localhost:9000 admin your_strong_password_here
# Create a dedicated user for Restic backups
mc admin user add local restic-user restic_password_here
# Create a policy with limited permissions
cat > restic-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::restic-backups/*", "arn:aws:s3:::restic-backups"]
}
]
}
EOF
mc admin policy create local restic-policy restic-policy.json
mc admin policy attach local restic-policy --user restic-user
Now your Restic job only has access to its own bucket, and a compromised credential can’t touch anything else.
Pointing Your Tools at MinIO
The S3 compatibility is what makes MinIO genuinely useful. Here’s how to configure the most common HomeLab tools:
Restic
export AWS_ACCESS_KEY_ID=restic-user
export AWS_SECRET_ACCESS_KEY=restic_password_here
export RESTIC_REPOSITORY=s3:http://your-server:9000/restic-backups
restic init
restic backup /path/to/data
rclone
rclone config create minio-local s3 \
provider=Minio \
access_key_id=your-access-key \
secret_access_key=your-secret \
endpoint=http://your-server:9000
# Test it
rclone ls minio-local:restic-backups
Nextcloud External Storage
Install the External Storage Support app, then add an S3-compatible storage entry pointing to http://your-server:9000 with your MinIO credentials. It works identically to a real AWS S3 bucket from Nextcloud’s perspective.
Tips from Running MinIO for Over a Year
These are the lessons that weren’t in the documentation:
Set Up Bucket Lifecycle Policies
Without lifecycle rules, old backups accumulate forever. Configure automatic expiry for buckets that don’t need long retention:
mc ilm add local/restic-backups --expiry-days 90
Enable Versioning for Critical Buckets
For any bucket storing config files or important application data, enable versioning. It saved me once when a broken backup script overwrote good data with a corrupted archive:
mc version enable local/config-backups
Monitor Disk Usage Proactively
MinIO doesn’t throttle writes when the disk is nearly full — it just starts failing silently. Add a simple cron check:
# Add to crontab: check every hour
0 * * * * df -h /opt/minio/data | awk 'NR==2{if($5+0 > 85) print "WARNING: MinIO disk at "$5}' | mail -s "MinIO Disk Alert" [email protected]
Put It Behind a Reverse Proxy for TLS
If you’re accessing MinIO from outside your LAN, add Nginx or Traefik in front with TLS. Sending S3 credentials over unencrypted HTTP is a real risk on any shared network segment. A basic Nginx config:
server {
listen 443 ssl;
server_name minio.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/minio.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/minio.yourdomain.com/privkey.pem;
# S3 API
location / {
proxy_pass http://localhost:9000;
proxy_set_header Host $host;
client_max_body_size 0; # Required for large uploads
}
}
Integrate with Your Existing Monitoring
MinIO exposes Prometheus metrics at /minio/v2/metrics/cluster. If you’re already running Prometheus and Grafana in your HomeLab, add MinIO as a scrape target and you get disk I/O, request latency, and per-bucket storage graphs without any extra work.
What MinIO Doesn’t Replace
Single-node MinIO has no built-in redundancy — a disk failure means data loss unless you’re running on a ZFS mirror or RAID underneath. Treat it as one tier of your backup strategy, not the only copy.
The rule I follow: critical data lives in MinIO (local, fast, always available) and gets synced offsite via rclone to Backblaze B2 (cheap cold storage at $6/TB/month). The 3-2-1 backup rule applies even when you’re self-hosting.
For anyone who’s been putting off cutting their cloud storage bill, the MinIO setup above takes about 20 minutes. Most S3-compatible tools will connect without any code changes — just swap the endpoint URL and credentials, and your existing backup jobs start writing to your own hardware.

