Skip to Content

Production Guide: Deploy MinIO with Docker Compose + NGINX + Let's Encrypt + UFW on Ubuntu

S3-compatible object storage with TLS, firewall hardening, and production-ready configuration.

Object storage is the backbone of modern data workflows: backups, media assets, ML training datasets, and application state all rely on scalable, S3-compatible storage. For teams that want full control over their data without locking into a cloud provider's proprietary storage API, MinIO is a leading open-source option. This guide walks through a production-grade MinIO deployment on Ubuntu using Docker Compose, NGINX reverse proxy, Let's Encrypt for automatic SSL, and UFW for firewall hardening.

Why MinIO for Production Workloads

MinIO is designed for high-performance object storage with full S3 compatibility, which means existing tools, SDKs, and backup scripts that work with AWS S3 will work seamlessly with your self-hosted MinIO cluster. Unlike cloud storage, you control retention, access policies, and data locality — critical for regulated industries or cost-sensitive teams ingesting terabytes of log or media data.

In production, a bare MinIO container is not enough. You need TLS termination, firewall rules, persistent storage configuration, and operational runbooks for upgrades and failures. This guide covers all of those, following the same production-first pattern as our other Guides posts.

Architecture Overview

The deployment includes these core components:

  • MinIO Server: Runs in a Docker container, stores data to a persistent host volume, exposes the S3 API and web console.
  • NGINX: Reverse proxy that terminates TLS (via Let's Encrypt) and routes traffic to MinIO's console and API ports.
  • Let's Encrypt: Provides free, auto-renewing SSL certificates for your domain.
  • UFW: Ubuntu's Uncomplicated Firewall, configured to only allow traffic on ports 80, 443, and SSH.

All services are defined in a single docker-compose.yml file for reproducibility and easy version control.

Prerequisites

  • Ubuntu 22.04 LTS or 24.04 LTS server
  • Domain name pointed to your server's public IP (e.g., minio.example.com)
  • Docker and Docker Compose installed (we'll cover this below if you haven't already)
  • Root or sudo access to the server
  • Basic familiarity with terminal commands and YAML syntax

Step 1: Install Docker and Docker Compose

If you don't have Docker installed yet, run these commands to install the latest stable version:

# Update package index
sudo apt update && sudo apt upgrade -y

# Install dependencies
sudo apt install -y ca-certificates curl gnupg lsb-release

# Add Docker GPG key
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# Add Docker repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install Docker Engine and Docker Compose plugin
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

Verify Docker is installed correctly:

sudo docker run hello-world

Step 2: Create MinIO Data Directory

Create a persistent directory on the host to store MinIO data, and set permissions so the MinIO container can write to it:

sudo mkdir -p /data/minio
sudo chown -R 1000:1000 /data/minio
sudo chmod -R 755 /data/minio

Step 3: Create docker-compose.yml

Create a directory for your MinIO deployment and add the docker-compose.yml file:

mkdir -p /opt/minio
nano /opt/minio/docker-compose.yml

Paste the following configuration into the file (replace minio.example.com with your actual domain):

version: '3.8'

services:
  minio:
    image: minio/minio:latest
    container_name: minio
    restart: unless-stopped
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: ${MINIO_ROOT_USER}
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
    volumes:
      - /data/minio:/data
    ports:
      - "9000:9000"
      - "9001:9001"
    networks:
      - minio-net

  nginx:
    image: nginx:alpine
    container_name: minio-nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/certs:/etc/nginx/certs
      - ./nginx/logs:/var/log/nginx
    depends_on:
      - minio
    networks:
      - minio-net

networks:
  minio-net:
    driver: bridge

Step 4: Configure MinIO Environment Variables

Create a .env file in the same directory to store your MinIO root credentials (never commit this to version control):

nano /opt/minio/.env

Add your secure credentials (use a password manager to generate a strong password):

MINIO_ROOT_USER=admin
MINIO_ROOT_PASSWORD=your-strong-password-here

Restrict permissions on the .env file:

chmod 600 /opt/minio/.env

Step 5: Configure NGINX Reverse Proxy

Create the NGINX configuration directory and add a site config for MinIO:

mkdir -p /opt/minio/nginx/conf.d
nano /opt/minio/nginx/conf.d/minio.conf

Paste the following configuration (replace minio.example.com with your domain):

server {
    listen 80;
    server_name minio.example.com;

    # Redirect all HTTP traffic to HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name minio.example.com;

    # SSL configuration will be added by Certbot

    location / {
        proxy_pass http://minio:9000;
        proxy_set_header Host $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;
    }

    location /console {
        proxy_pass http://minio:9001;
        proxy_set_header Host $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_set_header X-Forwarded-Prefix /console;
    }
}

Step 6: Set Up Let's Encrypt SSL Certificates

Install Certbot and the NGINX plugin to automatically generate and renew SSL certificates:

sudo apt install -y certbot python3-certbot-nginx

# Generate certificates (replace with your domain and email)
sudo certbot --nginx -d minio.example.com -m [email protected] --agree-tos --non-interactive

Certbot will automatically update your NGINX config to use the new SSL certificates and set up auto-renewal via a cron job.

Step 7: Configure UFW Firewall

Enable UFW and only allow traffic on ports 22 (SSH), 80 (HTTP), and 443 (HTTPS):

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Verify UFW rules:

sudo ufw status

Step 8: Start MinIO Services

Navigate to your MinIO directory and start the services with Docker Compose:

cd /opt/minio
sudo docker compose up -d

Check that all containers are running:

sudo docker compose ps

Step 9: Verify Your Deployment

Open your browser and navigate to https://minio.example.com/console (replace with your domain). Log in with the MINIO_ROOT_USER and MINIO_ROOT_PASSWORD you set in the .env file.

Test uploading a file via the web console, then verify it's accessible via the S3 API:

# Install MinIO client
sudo docker pull minio/mc

# Configure MinIO host
sudo docker run --rm -it minio/mc alias set myminio https://minio.example.com admin your-strong-password-here

# Create a test bucket
sudo docker run --rm minio/mc mb myminio/test-bucket

# Upload a test file
echo "Hello MinIO" > test.txt
sudo docker run --rm -v $(pwd):/data minio/mc cp /data/test.txt myminio/test-bucket

Configuration Best Practices

  • Secrets Management: Never commit your .env file to version control. Use a secrets manager for production credentials.
  • Persistent Storage: Ensure /data/minio is on a dedicated disk with enough capacity for your workload.
  • Backups: Regularly back up MinIO data to an offsite location (e.g., another MinIO instance or cloud storage).
  • Monitoring: Integrate MinIO with Prometheus (see our VictoriaMetrics guide for metrics setup).

Common Issues & Fixes

  • MinIO container won't start: Check permissions on /data/minio. Run sudo chown -R 1000:1000 /data/minio again.
  • SSL certificate errors: Ensure your domain's DNS is correctly pointed to your server. Check Certbot logs at /var/log/letsencrypt/letsencrypt.log.
  • NGINX proxy errors: Verify the MinIO container is running and accessible on the internal network. Run sudo docker exec -it minio ping minio from the NGINX container.
  • Permission denied on .env: Ensure the .env file is owned by root and has 600 permissions.

FAQ

How do I scale MinIO for more storage?

MinIO supports distributed mode, where you can add more nodes to increase storage capacity and throughput. Update your docker-compose.yml to include multiple MinIO containers with different data volumes, and use the MINIO_SERVER_URL environment variable to configure the cluster.

Can I use MinIO with S3-compatible tools?

Yes, MinIO is fully S3-compatible. You can use AWS SDKs, s3cmd, MinIO Client (mc), and most backup tools that support S3 without any changes.

How do I set up bucket policies for public read access?

Use the MinIO web console or the mc client to set bucket policies. For example, to make a bucket publicly readable: mc anonymous set download myminio/public-bucket.

What's the difference between the S3 API port (9000) and console port (9001)?

Port 9000 is for programmatic S3 API access (used by SDKs and tools), while port 9001 is for the web-based management console where you can create buckets, upload files, and manage users.

How do I renew Let's Encrypt certificates?

Certbot sets up automatic renewal by default. You can test renewal with sudo certbot renew --dry-run.

Can I run MinIO behind a different reverse proxy like Traefik?

Yes, we cover Traefik-based deployments in our OpenObserve guide. The core MinIO configuration remains the same; only the proxy config changes.

How do I monitor MinIO health?

MinIO exposes Prometheus metrics at /minio/v2/metrics. You can scrape these with Prometheus and visualize them in Grafana (see our Grafana guide for setup steps).

Internal Resources

Talk to us

Need help deploying MinIO or designing a production object storage strategy? Our team can help you build a reliable, scalable setup tailored to your workload, including distributed clusters, backup policies, and monitoring integrations.

Contact Us

Production Guide: Deploy Graylog with Docker Compose + Traefik + OpenSearch + MongoDB on Ubuntu
A production-oriented Graylog deployment pattern with Docker Compose, Traefik TLS routing, OpenSearch storage, MongoDB metadata, secure secrets, verification, and recovery notes.