MinIO Self-Host Setup: Build S3-Compatible Object Storage in 30 Minutes
Cloud object storage bills creep up. Egress fees sting. And sometimes you just need data sitting on hardware you control. MinIO gives you a drop-in S3-compatible API without the AWS lock-in. This guide walks you through a production-ready MinIO self-host setup—from a single Docker container to a secured, bucket-polished object store you can actually use.
If you want a faster, more focused walkthrough, check out MinIO Self-Host Setup: Build Your Own S3-Compatible Object Storage in Under an Hour.
What You Will Build
By the end of this guide you will have:
- MinIO running in Docker with persistent storage
- Erasure-coded data protection against disk failure
- HTTPS access behind a reverse proxy
- Bucket policies and user access controls configured
- A working S3 endpoint tested with the AWS CLI
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 and YAML
- Ports 9000 (S3 API) and 9001 (console) available
Verify Docker is ready:
docker --version
docker compose version
If not installed, run:
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, the volumes, and the environment.
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.2025-07-11T18-11-10Z
container_name: minio
restart: unless-stopped
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: ChangeMeInProduction123
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: Add Erasure Coding with Multiple Drives
A single drive is fine 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 if you want rollback protection
- 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: Integrate MinIO into Your Application Stack
MinIO shines when it is part of a larger platform. If you are running Budibase, N8N, or any app that needs S3-backed file storage, MinIO slots right in.
Here is a typical environment configuration for an application connecting to MinIO:
S3_ENDPOINT=https://s3.yourdomain.com
S3_BUCKET=app-uploads
S3_ACCESS_KEY=your-access-key
S3_SECRET_KEY=your-secret-key
S3_REGION=us-east-1
S3_FORCE_PATH_STYLE=true
The key setting is S3_FORCE_PATH_STYLE=true. MinIO uses path-style URLs (https://s3.yourdomain.com/bucket-name) rather than virtual-hosted style (https://bucket-name.s3.yourdomain.com). Most S3 SDKs need this flag to work correctly with self-hosted endpoints.
If you are building a full application platform, you might find our Production Guide: Deploy Budibase with Docker Compose + Caddy + CouchDB + Redis + MinIO on Ubuntu useful. It shows how MinIO fits into a real multi-service architecture.
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, and S3 API compatibility. That is more than most teams start with. From here, consider:
- Enabling bucket versioning and object locking for compliance
- Setting up lifecycle rules to move old objects to cold storage
- Configuring bucket notifications to trigger webhooks on uploads
- Running MinIO in distributed mode across multiple servers for true HA
- Using the MinIO Client (
mc) for scripting and batch operations
Object storage does not have to mean cloud vendor lock-in. With MinIO, you own the stack, the data, and the bill.
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.