Deploying a Distributed MinIO Object Storage Cluster with Docker Compose: Build Your Own On-Premises S3

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Real-World Challenges: Cloud Cost Traps and System Outage Risks

When an application needs to store large volumes of images, videos, or backup files, AWS S3 is often the first choice. But once storage exceeds the 5TB to 10TB threshold, the bills begin to skyrocket. The most painful part is data egress fees, which average around $0.09 per GB. This cost quickly becomes a heavy burden for scaling projects.

Many teams opt to run a standalone MinIO container on an internal server. This approach is fast and budget-friendly. However, it introduces a Single Point of Failure. If the hard drive experiences physical failure or the process crashes in the middle of the night, the entire service goes down instantly.

To solve this problem, you need to transition to Distributed MinIO. This architecture preserves full Amazon S3 API compatibility while ensuring High Availability without depending on expensive cloud infrastructure.

Quick Overview: Distributed MinIO and Erasure Coding

In distributed mode, MinIO pools multiple drives across containers or physical servers into a single unified storage space.

How Does Erasure Coding Protect Your Data?

Unlike traditional replication mechanisms that double or triple storage overhead, MinIO uses the Erasure Coding algorithm (based on Reed-Solomon). When you upload a file, the system automatically splits it into data blocks and parity blocks, distributing them across all drives in the cluster.

Suppose you set up a 4-drive cluster with the default configuration (2 data blocks + 2 parity blocks). In this case, usable storage efficiency is 50%. The key benefit is that even if 2 out of the 4 drives fail simultaneously, the system can still read and reconstruct the original file with 100% integrity.

Minimum Cluster Requirements

  • A minimum of 4 independent drives (or 4 containers, each mounted with a dedicated volume).
  • Nodes must communicate seamlessly over an internal network using the same credentials (Root User/Password).
  • A reverse proxy (such as Nginx) in front to load-balance API request traffic across nodes.

Hands-on: Setting Up a 4-Node MinIO Cluster and Nginx with Docker Compose

A classic rookie mistake is pointing all 4 MinIO containers to the exact same directory on the host machine. This leads to file-lock contention and constant container crashes. Always remember: each MinIO node must have its own dedicated data volume.

Step 1: Set Up the Directory Structure

Open your terminal and create the project directory:

mkdir -p ~/distributed-minio/nginx
cd ~/distributed-minio

Step 2: Configure Nginx as a Load Balancer

Create the nginx/nginx.conf file to load balance S3 API traffic (port 9000) and Web Console traffic (port 9001):

events {
    worker_connections 1024;
}

http {
    upstream minio_s3_servers {
        least_conn;
        server minio1:9000;
        server minio2:9000;
        server minio3:9000;
        server minio4:9000;
    }

    upstream minio_console_servers {
        ip_hash;
        server minio1:9001;
        server minio2:9001;
        server minio3:9001;
        server minio4:9001;
    }

    server {
        listen 9000;
        server_name localhost;

        client_max_body_size 0;
        proxy_buffering off;
        proxy_request_buffering off;

        location / {
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_connect_timeout 300;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            chunked_transfer_encoding off;
            proxy_pass http://minio_s3_servers;
        }
    }

    server {
        listen 9001;
        server_name localhost;

        location / {
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_pass http://minio_console_servers;
        }
    }
}

Step 3: Create the docker-compose.yml File

Create the docker-compose.yml file in the project root directory ~/distributed-minio:

version: '3.8'

services:
  minio1:
    image: minio/minio:RELEASE.2024-03-30T09-41-56Z
    hostname: minio1
    volumes:
      - data1-1:/data1
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: MySecretPassword123
    command: server http://minio{1...4}/data1 --console-address ":9001"
    networks:
      - minio-net

  minio2:
    image: minio/minio:RELEASE.2024-03-30T09-41-56Z
    hostname: minio2
    volumes:
      - data2-1:/data1
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: MySecretPassword123
    command: server http://minio{1...4}/data1 --console-address ":9001"
    networks:
      - minio-net

  minio3:
    image: minio/minio:RELEASE.2024-03-30T09-41-56Z
    hostname: minio3
    volumes:
      - data3-1:/data1
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: MySecretPassword123
    command: server http://minio{1...4}/data1 --console-address ":9001"
    networks:
      - minio-net

  minio4:
    image: minio/minio:RELEASE.2024-03-30T09-41-56Z
    hostname: minio4
    volumes:
      - data4-1:/data1
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: MySecretPassword123
    command: server http://minio{1...4}/data1 --console-address ":9001"
    networks:
      - minio-net

  nginx:
    image: nginx:alpine
    ports:
      - "9000:9000"
      - "9001:9001"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - minio1
      - minio2
      - minio3
      - minio4
    networks:
      - minio-net

volumes:
  data1-1:
  data2-1:
  data3-1:
  data4-1:

networks:
  minio-net:
    driver: bridge

Step 4: Start and Verify the Cluster

Start the cluster services in the background:

docker compose up -d

Check the list of running containers:

docker compose ps

Open your browser and navigate to http://localhost:9001. Log in with the username minioadmin and password MySecretPassword123. Navigate to Monitoring > Metrics; you should see a healthy green status showing 4/4 Online Drives.

Step 5: Test the Connection with Python (Boto3)

Since MinIO is 100% compatible with the S3 API, you simply need to pass the endpoint_url parameter into the Boto3 client:

import boto3
from botocore.client import Config

s3_client = boto3.client(
    's3',
    endpoint_url='http://localhost:9000',
    aws_access_key_id='minioadmin',
    aws_secret_access_key='MySecretPassword123',
    config=Config(signature_version='s3v4'),
    region_name='us-east-1'
)

# 1. Create a new bucket
bucket_name = 'app-media-storage'
try:
    s3_client.create_bucket(Bucket=bucket_name)
    print(f"Created bucket: {bucket_name}")
except Exception as e:
    print(f"Bucket already exists or encountered an error: {e}")

# 2. Upload sample data file
file_content = b"Hello MinIO Distributed Cluster running on Docker Compose!"
s3_client.put_object(Bucket=bucket_name, Key='sample.txt', Body=file_content)
print("Uploaded sample.txt successfully!")

# 3. Read data from the bucket
response = s3_client.get_object(Bucket=bucket_name, Key='sample.txt')
print("File content from MinIO:", response['Body'].read().decode('utf-8'))

Best Practices for Production Deployment

Setting up via Docker Compose on a single host is great for staging or testing environments. However, when moving to production, keep these three considerations in mind:

  • Separate Physical Servers: Place each MinIO node on a dedicated server or VM to eliminate the risk of a total cluster outage caused by single-host hardware failure.
  • Network Security: Configure SSL/TLS (HTTPS) on Nginx and immediately change the default root credentials.
  • Write Quorum: For a 4-drive cluster, MinIO requires at least N/2 + 1 (i.e., 3 operational drives) to process write operations. Plan your capacity and node count according to project requirements.
Share: