MinIO Self-Host Setup: Production-Ready S3-Compatible Object Storage
Cloud object storage bills grow silently. Egress fees surprise you at month-end. And sometimes you simply need data on hardware you control. MinIO gives you a drop-in S3-compatible API without vendor lock-in. This guide walks you through a production-grade MinIO self-host setup—from single-node Docker deployment to erasure-coded storage, bucket policies, and HTTPS termination you can actually run in production.
If you want a faster walkthrough focused on speed, check out MinIO Self-Host Setup: Build S3-Compatible Object Storage in 30 Minutes.
What You Will Build
By the end of this guide you will have:
- MinIO running in Docker with erasure-coded data protection
- HTTPS access behind a reverse proxy with auto-renewing certificates
- Bucket policies and scoped user access controls
- Versioning and lifecycle rules configured
- A working S3 endpoint tested with the AWS CLI and SDK
Prerequisites
Before you start, gather the following:
- A Linux server (Ubuntu 22.04+ or Debian 12+) with at least 2 CPU cores, 4 GB RAM, and 50 GB free disk
- Docker and Docker Compose installed
- A domain or subdomain pointed at your server (for HTTPS)
- Basic comfort with the command line, YAML, and S3 concepts
- Ports 9000 (S3 API) and 9001 (console) available
Verify Docker is ready:
docker --version
docker compose version
If not installed:
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
Step 1: Deploy MinIO with Docker Compose
The simplest reliable MinIO self-host setup uses Docker Compose. One file defines the service, volumes, environment, and health checks.
Create a project directory:
mkdir ~/minio-stack && cd ~/minio-stack
mkdir -p data
Write a docker-compose.yml:
version: "3.8"
services:
minio:
image: quay.io/minio/minio:RELEASE.2026-01-18T03-15-33Z
container_name: minio
restart: unless-stopped
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: Change…n123
MINIO_BROWSER: "on"
volumes:
- ./data:/data
command: server /data --console-address ":9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
Start the container:
docker compose up -d
Open the MinIO Console at http://your-server-ip:9001 and log in with the root credentials from your Compose file.
Step 2: Enable Erasure Coding with Multiple Drives
A single drive works for testing. For anything serious, you want erasure coding. MinIO stripes data across drives and can survive losing up to half of them.
MinIO requires drives in sets of 4, 6, 8, 10, 12, 14, or 16. For a standalone server, use 4 drives:
mkdir -p data/disk{1..4}
Update the command in your Compose file:
volumes:
- ./data/disk1:/data1
- ./data/disk2:/data2
- ./data/disk3:/data3
- ./data/disk4:/data4
command: server http://minio/data{1...4} --console-address ":9001"
Recreate the container:
docker compose down
docker compose up -d
Check the console. You should see 4 drives online and a parity ratio displayed. With 4 drives, MinIO uses 2 parity drives—you can lose 2 drives without losing data.
Step 3: Configure Users, Buckets, and Policies
Running everything as root user is a liability. Create scoped users and lock down bucket access with policies.
Create a Bucket
From the MinIO Console:
- Click Buckets → Create Bucket
- Name it
app-uploads - Enable Versioning for rollback protection
- Enable Object Locking only if you need compliance (this is irreversible)
- Click Create
Create a Read-Write User Policy
Go to Policies → Create Policy and paste this JSON:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation",
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::app-uploads",
"arn:aws:s3:::app-uploads/*"
]
}
]
}
Create a Service User
Go to Users → Create User. Name it app-service, assign the policy above, and save the generated access and secret keys. You will need them for your application.
For a deeper dive into policy patterns and multi-user setups, see MinIO Self-Host Setup: The Developer's Guide to S3-Compatible Storage.
Step 4: Test the S3 API with the AWS CLI
MinIO speaks S3. That means any tool built for AWS S3 works here with only endpoint and credential changes.
Install the AWS CLI if you do not have it:
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
Configure a profile for your MinIO instance:
aws configure --profile minio
# AWS Access Key ID: your-minio-access-key
# AWS Secret Access Key: your-minio-secret-key
# Default region: us-east-1
# Default output: json
Test a bucket listing:
aws --profile minio --endpoint-url http://localhost:9000 s3 ls
Upload a file:
echo "hello minio" > test.txt
aws --profile minio --endpoint-url http://localhost:9000 s3 cp test.txt s3://app-uploads/test.txt
Download it back:
aws --profile minio --endpoint-url http://localhost:9000 s3 cp s3://app-uploads/test.txt -
If that prints hello minio, your S3-compatible endpoint is live.
Step 5: Secure MinIO with HTTPS and a Reverse Proxy
Exposing port 9000 over HTTP is fine for local testing. In production, you need TLS. The easiest path is Caddy.
Add Caddy to Your Compose File
caddy:
image: caddy:2.8
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
- caddy-config:/config
networks:
- minio-net
volumes:
caddy-data:
caddy-config:
Create Caddyfile:
s3.yourdomain.com {
reverse_proxy minio:9000
}
console.yourdomain.com {
reverse_proxy minio:9001
}
Update your MinIO service to join the same network:
networks:
- minio-net
networks:
minio-net:
driver: bridge
Restart the stack:
docker compose up -d
Caddy will fetch certificates from Let's Encrypt automatically. Your S3 API is now at https://s3.yourdomain.com and the console at https://console.yourdomain.com.
Step 6: Enable Versioning and Lifecycle Rules
Production storage needs more than buckets. You need versioning for recovery and lifecycle rules for cost control.
Enable Versioning
From the MinIO Console:
- Go to Buckets → app-uploads → Manage
- Click the Versioning tab
- Toggle Enable Versioning
Now every overwrite creates a new version. You can recover deleted or corrupted objects.
Add a Lifecycle Rule
Old versions accumulate storage. Set a lifecycle rule to expire them after 30 days:
- Go to Buckets → app-uploads → Lifecycle
- Click Add Lifecycle Rule
- Set Rule Name to
expire-old-versions - Choose Expire non-current versions
- Set Non-current days to
30 - Click Save
This keeps your storage costs predictable. Current objects stay forever. Old versions clean themselves up.
Tips and Troubleshooting
MinIO console shows a blank page
- Make sure
MINIO_BROWSER=onis set in your environment - Check that port 9001 is not blocked by a firewall:
sudo ufw allow 9001
Cannot connect with AWS CLI
- Verify the endpoint URL includes the protocol:
http://orhttps:// - Confirm the access key has the correct policy attached in the MinIO Console
- If using HTTPS with a self-signed certificate, add
--no-verify-sslto your AWS CLI commands
Data disappears after container restart
- Ensure your
volumesmap host directories, not anonymous Docker volumes - Without a host bind mount, data lives inside the container and is destroyed on removal
Erasure coding will not start
- Drive counts must be 4, 6, 8, 10, 12, 14, or 16 per erasure set
- All drives in a set must be empty when MinIO first starts, or it will refuse to format them
Slow upload or download speeds
- Check disk I/O with
iotoporiostat. Spinning rust will bottleneck large file transfers - Ensure your network interface is not saturated. MinIO benefits from 10 Gbps+ in production
- Run MinIO on locally attached drives. Network-attached storage adds latency
Permission denied on the data directory
- MinIO runs as a non-root user inside the container. Fix ownership on the host:
sudo chown -R 1000:1000 ./data
Next Steps
You now have a working MinIO self-host setup with erasure coding, user policies, HTTPS, versioning, and S3 API compatibility. That is more than most teams start with. From here, consider:
- Running MinIO in distributed mode across multiple servers for true high availability
- Configuring bucket replication to a second MinIO cluster for disaster recovery
- Setting up bucket notifications to trigger webhooks on uploads
- Using the MinIO Client (
mc) for scripting, batch operations, and policy management - Integrating MinIO with backup tools like Restic or Velero for off-site backups
Object storage does not have to mean cloud vendor lock-in. With MinIO, you own the stack, the data, and the bill.
For a focused walkthrough on getting started quickly, see MinIO Self-Host Setup: Build Your Own S3-Compatible Object Storage in Under an Hour.
Need Help at Scale?
If you are running production workloads and need distributed MinIO, SSO integration, fine-grained IAM, or migration from AWS S3, we can help. Contact the Sysbrix team and we will architect an object storage setup that fits your stack.