Storage intermediate ⏱ 45 minutes

MinIO: Self-Hosted S3-Compatible Object Storage

Run your own S3-compatible storage. Perfect for backups, static sites, and cloud-native apps.

By homelabbing.io • 2025-12-16
#minio#s3#object-storage#cloud-native#backup

Deploy your own high-performance, S3-compatible object storage with MinIO. This comprehensive guide shows you how to build a robust, scalable storage backend for backups, static sites, application data, and more - all within your homelab.

Difficulty: Intermediate Estimated Time: 45 minutes Last Updated: December 2025 Target Audience: Homelab administrators, developers, backup enthusiasts


What You’ll Learn

  • Deploy MinIO for S3-compatible object storage
  • Configure buckets and access policies
  • Use the MinIO Client (mc) CLI tool
  • Integrate with backup solutions (Restic, Borg, Duplicati)
  • Set up lifecycle policies for automatic data management
  • Configure versioning and object locking
  • Implement high availability with erasure coding
  • Monitor storage metrics and health
  • Secure MinIO with TLS and access controls

Prerequisites

Required Knowledge

  • Docker and Docker Compose fundamentals
  • Basic understanding of object storage concepts
  • Linux command-line proficiency
  • Networking basics (ports, DNS, reverse proxy)

Required Infrastructure

  • Linux server or NAS (Ubuntu 22.04+, Debian 11+)
  • Minimum: 2 GB RAM, 2 CPU cores, 20 GB storage
  • Recommended: 8+ GB RAM, 4+ cores, dedicated storage drives
  • Docker and Docker Compose installed
  • Adequate storage space for object data

Pre-Deployment Checklist

  • Storage location planned (dedicated drives recommended)
  • Network configuration verified
  • Access credentials generated
  • Reverse proxy configured (for HTTPS)
  • Backup destinations identified

1. Understanding MinIO

What is MinIO?

MinIO is a high-performance, S3-compatible object storage system designed for cloud-native applications. It’s open-source, lightweight, and perfect for self-hosted environments where you need scalable storage without AWS costs.

Key Features:

  • S3 Compatible: Works with any S3-compatible application
  • High Performance: Optimized for modern hardware (NVMe, SSDs)
  • Distributed: Scale from single-node to petabyte-scale clusters
  • Erasure Coding: Data protection without RAID overhead
  • Object Versioning: Keep multiple versions of objects
  • Lifecycle Policies: Automatic data expiration and tiering
  • Encryption: Server-side and client-side encryption support
  • Multi-Tenancy: Separate buckets with access controls
  • Web Console: Beautiful, intuitive management interface

Why Use MinIO in Your Homelab?

Common Use Cases:

  1. Backup Backend: Store Restic, Borg, Duplicati, or Veeam backups
  2. Static Site Hosting: Serve static websites and media
  3. Application Storage: Backend for apps needing object storage
  4. Media Archives: Store photos, videos, and documents
  5. CI/CD Artifacts: Store build artifacts and container images
  6. Logging and Monitoring: Store logs, metrics, and traces
  7. Data Lake: Centralized storage for data analytics

MinIO vs. Alternatives

FeatureMinIOAWS S3CephNextcloud
S3 CompatibleYesYes (original)YesNo
Self-HostedYesNoYesYes
Setup ComplexityEasyN/AComplexEasy
PerformanceExcellentExcellentGoodModerate
ScalabilityExcellentUnlimitedExcellentLimited
CostFreePay-per-useFreeFree
Web InterfaceExcellentGoodBasicExcellent
Use CaseObject storageCloud storageAll-purposeFile sync/share

How MinIO Works

Architecture:

Clients (Restic, Apps, mc CLI)
        ↓
    MinIO Server
        ↓
   Object Storage
        ↓
Buckets → Objects (files)

Bucket and Object Model:

MinIO Storage
├── bucket-backups/
│   ├── restic/
│   │   ├── config
│   │   ├── data/
│   │   └── keys/
│   └── borg/
├── bucket-media/
│   ├── photos/
│   └── videos/
└── bucket-static-sites/
    └── website/
        ├── index.html
        └── assets/

Erasure Coding:

MinIO splits objects into data and parity chunks for redundancy:

Original Object (10 MB)
    ↓
Split into 4 data chunks + 2 parity chunks
    ↓
Can lose any 2 chunks and still recover data
    ↓
Storage efficiency: Better than RAID 6

2. Installing MinIO

Single-Node Docker Deployment

Create directory structure:

# Create MinIO directories
mkdir -p /opt/minio/{data,config}
cd /opt/minio

Create docker-compose.yml:

version: '3.8'

services:
  minio:
    image: minio/minio:latest
    container_name: minio
    hostname: minio
    restart: unless-stopped
    command: server /data --console-address ":9001"
    environment:
      # Root credentials (change these!)
      MINIO_ROOT_USER: admin
      MINIO_ROOT_PASSWORD: changeme-secure-password

      # Optional: Enable browser access
      MINIO_BROWSER: "on"

      # Optional: Domain configuration
      MINIO_DOMAIN: minio.yourhomelab.local
      MINIO_SERVER_URL: https://s3.yourhomelab.local

    ports:
      # S3 API endpoint
      - "9000:9000"
      # Web console
      - "9001:9001"

    volumes:
      # Object data storage
      - /mnt/storage/minio-data:/data
      # Configuration
      - ./config:/root/.minio

    networks:
      - minio-network

    # Resource limits
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 4G
        reservations:
          cpus: '1'
          memory: 2G

    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

networks:
  minio-network:
    driver: bridge

Secure the configuration:

# Create strong credentials
MINIO_ROOT_USER="admin"
MINIO_ROOT_PASSWORD=$(openssl rand -base64 32)

# Save to .env file
cat > .env <<EOF
MINIO_ROOT_USER=$MINIO_ROOT_USER
MINIO_ROOT_PASSWORD=$MINIO_ROOT_PASSWORD
EOF

# Protect .env file
chmod 600 .env

# Update docker-compose.yml to use env_file

Updated docker-compose.yml:

services:
  minio:
    # ... (previous config)
    env_file:
      - .env
    environment:
      MINIO_BROWSER: "on"

Deploy MinIO:

# Start MinIO
docker-compose up -d

# View logs
docker-compose logs -f minio

# Verify status
docker-compose ps
curl http://localhost:9000/minio/health/live

Access the web console:

Open http://your-server-ip:9001 and login with your credentials.

Multi-Drive Deployment (Better Performance)

For homelabs with multiple drives, use distributed mode:

version: '3.8'

services:
  minio:
    image: minio/minio:latest
    container_name: minio
    restart: unless-stopped
    # Use multiple drives for erasure coding
    command: server /data{1...4} --console-address ":9001"
    environment:
      MINIO_ROOT_USER: admin
      MINIO_ROOT_PASSWORD: your-secure-password
    ports:
      - "9000:9000"
      - "9001:9001"
    volumes:
      # Map to 4 different drives
      - /mnt/disk1:/data1
      - /mnt/disk2:/data2
      - /mnt/disk3:/data3
      - /mnt/disk4:/data4
    networks:
      - minio-network

This provides erasure coding across drives, allowing one drive failure without data loss.

Native Installation (No Docker)

Download and install:

# Download MinIO server
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
sudo mv minio /usr/local/bin/

# Verify
minio --version

Create systemd service:

sudo nano /etc/systemd/system/minio.service

Add:

[Unit]
Description=MinIO Object Storage
Documentation=https://min.io/docs
After=network.target

[Service]
Type=notify
User=minio
Group=minio
Environment="MINIO_ROOT_USER=admin"
Environment="MINIO_ROOT_PASSWORD=your-secure-password"
ExecStart=/usr/local/bin/minio server /mnt/storage/minio-data --console-address ":9001"
Restart=always
LimitNOFILE=65536
TasksMax=infinity
TimeoutStopSec=infinity
SendSIGKILL=no

[Install]
WantedBy=multi-user.target

Create user and directories:

# Create minio user
sudo useradd -r -s /sbin/nologin minio

# Create data directory
sudo mkdir -p /mnt/storage/minio-data
sudo chown -R minio:minio /mnt/storage/minio-data

# Enable and start service
sudo systemctl daemon-reload
sudo systemctl enable minio.service
sudo systemctl start minio.service
sudo systemctl status minio.service

3. Initial Configuration

First Login and Setup

Access web console:

  1. Navigate to http://your-server-ip:9001
  2. Login with root credentials
  3. You’ll see the MinIO dashboard

Creating Access Keys

For applications and CLI tools, create access keys instead of using root credentials.

Via Web Console:

  1. Click Identity → Users
  2. Click Create User
  3. Enter username (e.g., backup-user)
  4. Generate password
  5. Click Create
  6. Assign policies (e.g., readwrite or custom)
  7. Note the Access Key and Secret Key

Via mc CLI:

# We'll install mc CLI in the next section
mc admin user add myminio backup-user SecurePassword123
mc admin policy attach myminio readwrite --user backup-user

Creating Your First Bucket

Via Web Console:

  1. Click Buckets → Create Bucket
  2. Enter bucket name (e.g., backups)
  3. Configure settings:
    • Versioning: Enable/Disable
    • Object Locking: Enable for immutable backups
    • Quota: Set size limit (optional)
  4. Click Create Bucket

Via mc CLI:

mc mb myminio/backups

4. MinIO Client (mc) Setup

Installing mc CLI

Linux:

# Download
wget https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
sudo mv mc /usr/local/bin/

# Verify
mc --version

macOS:

brew install minio/stable/mc

Docker:

# Run as container
docker run -it --rm minio/mc --help

Configuring mc

Add MinIO server:

# Syntax: mc alias set <ALIAS> <URL> <ACCESS_KEY> <SECRET_KEY>
mc alias set myminio http://192.168.1.100:9000 admin your-password

# Verify connection
mc admin info myminio

For HTTPS:

mc alias set myminio https://s3.yourhomelab.local admin your-password

List configured aliases:

mc alias list

Basic mc Commands

Bucket operations:

# List buckets
mc ls myminio

# Create bucket
mc mb myminio/my-new-bucket

# Remove empty bucket
mc rb myminio/my-new-bucket

# Remove bucket with contents (use with caution!)
mc rb --force myminio/my-new-bucket

Object operations:

# Upload file
mc cp /path/to/file.txt myminio/backups/

# Upload directory
mc cp --recursive /path/to/directory/ myminio/backups/

# Download file
mc cp myminio/backups/file.txt /path/to/destination/

# List objects
mc ls myminio/backups/

# Remove object
mc rm myminio/backups/file.txt

# Remove all objects in path
mc rm --recursive --force myminio/backups/old-files/

Sync operations:

# Sync local directory to bucket (one-way)
mc mirror /local/path/ myminio/backups/

# Sync bucket to local (download)
mc mirror myminio/backups/ /local/path/

# Watch for changes and sync continuously
mc mirror --watch /local/path/ myminio/backups/

Advanced operations:

# Find files
mc find myminio/backups --name "*.jpg"

# Find files larger than 100MB
mc find myminio/backups --larger 100MB

# Copy between buckets
mc cp myminio/source-bucket/file.txt myminio/dest-bucket/

# Diff between local and bucket
mc diff /local/path/ myminio/backups/

5. Integration with Backup Tools

Restic Integration

Configure Restic to use MinIO:

# Set environment variables
export RESTIC_REPOSITORY="s3:http://192.168.1.100:9000/backups"
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
export RESTIC_PASSWORD="your-restic-password"

# Initialize repository
restic init

# Create backup
restic backup /data

# List snapshots
restic snapshots

For HTTPS (with custom CA):

export RESTIC_REPOSITORY="s3:https://s3.yourhomelab.local/backups"
export AWS_CA_BUNDLE="/path/to/ca-cert.pem"

Duplicati Integration

Configure Duplicati to use MinIO:

  1. Create new backup
  2. Storage Type: S3 Compatible
  3. Settings:
    Server: s3.yourhomelab.local:9000
    Bucket name: backups
    Folder path: duplicati/
    AWS Access ID: your-access-key
    AWS Secret Key: your-secret-key
    Use SSL: ☐ (if using HTTP) or ☑ (if HTTPS)
  4. Test connection
  5. Continue with backup configuration

Borg Integration (via rclone)

Borg doesn’t natively support S3, but can use rclone as a backend:

Install rclone:

sudo apt install rclone

Configure rclone for MinIO:

rclone config

# Choose: n (New remote)
# Name: minio
# Storage: s3 (Amazon S3)
# Provider: Minio
# Access Key: your-access-key
# Secret Key: your-secret-key
# Endpoint: http://192.168.1.100:9000
# Location constraint: (leave empty)
# ACL: (leave default)

Mount MinIO via rclone:

# Mount bucket as filesystem
mkdir -p /mnt/minio-backups
rclone mount minio:backups /mnt/minio-backups --daemon

# Now use with Borg
borg init /mnt/minio-backups/borg-repo
borg create /mnt/minio-backups/borg-repo::archive /data

Veeam Integration

Veeam supports S3 natively:

  1. Add Repository → Object Storage
  2. Type: S3 Compatible
  3. Settings:
    Service Point: http://192.168.1.100:9000
    Region: us-east-1 (or any)
    Access Key: your-access-key
    Secret Key: your-secret-key
    Bucket: veeam-backups
    Folder: backups/
  4. Test connection
  5. Save

6. Access Policies and Security

Understanding MinIO Policies

MinIO uses AWS IAM-compatible policies for access control.

Built-in policies:

  • readonly: Read-only access to all buckets
  • readwrite: Read/write access to all buckets
  • writeonly: Upload-only, no read
  • diagnostics: Admin diagnostics access
  • consoleAdmin: Full admin access via console

Assign policy to user:

mc admin policy attach myminio readwrite --user backup-user

Creating Custom Policies

Example: Bucket-specific access:

Create policy file backup-only.json:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": [
        "arn:aws:s3:::backups/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::backups"
      ]
    }
  ]
}

Apply policy:

mc admin policy create myminio backup-only backup-only.json
mc admin policy attach myminio backup-only --user backup-user

Example: Read-only public bucket:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": ["*"]
      },
      "Action": [
        "s3:GetObject"
      ],
      "Resource": [
        "arn:aws:s3:::public-files/*"
      ]
    }
  ]
}

Apply to bucket:

mc anonymous set download myminio/public-files

Bucket-Level Policies

Set bucket policy via mc:

# Make bucket publicly readable
mc anonymous set public myminio/public-files

# Make bucket downloadable (not listable)
mc anonymous set download myminio/downloads

# Make bucket uploadable
mc anonymous set upload myminio/uploads

# Remove public access
mc anonymous set none myminio/private-bucket

Enabling HTTPS/TLS

Generate certificates:

# Self-signed certificate (for testing)
mkdir -p /opt/minio/certs
openssl req -x509 -newkey rsa:4096   -keyout /opt/minio/certs/private.key   -out /opt/minio/certs/public.crt   -days 365 -nodes   -subj "/CN=s3.yourhomelab.local"

Configure MinIO (Docker):

services:
  minio:
    # ... (previous config)
    volumes:
      - ./certs:/root/.minio/certs
    environment:
      MINIO_SERVER_URL: https://s3.yourhomelab.local:9000

Or for native installation:

sudo mkdir -p /home/minio/.minio/certs
sudo cp public.crt /home/minio/.minio/certs/
sudo cp private.key /home/minio/.minio/certs/
sudo chown -R minio:minio /home/minio/.minio/certs
sudo chmod 600 /home/minio/.minio/certs/private.key

Restart MinIO:

sudo systemctl restart minio

Use Let’s Encrypt with reverse proxy (recommended):

Use Nginx Proxy Manager or Caddy to provide HTTPS:

Caddy example:

s3.yourhomelab.local {
    reverse_proxy localhost:9000
    tls [email protected]
}

console.yourhomelab.local {
    reverse_proxy localhost:9001
    tls [email protected]
}

7. Versioning and Object Locking

Object Versioning

Versioning keeps multiple versions of objects, protecting against accidental deletion or modification.

Enable versioning on bucket:

# Via mc CLI
mc version enable myminio/backups

# Verify
mc version info myminio/backups

Via Web Console:

  1. Go to bucket
  2. Click Settings
  3. Enable Versioning

List object versions:

mc ls --versions myminio/backups/file.txt

Restore previous version:

# Download specific version
mc cp --version-id=VERSION_ID myminio/backups/file.txt ./restored-file.txt

Delete specific version:

mc rm --version-id=VERSION_ID myminio/backups/file.txt

Object Locking (Immutability)

Protect objects from deletion or modification for compliance/ransomware protection.

Enable object locking (must be set at bucket creation):

mc mb --with-lock myminio/immutable-backups

Set retention policy:

# Retention mode: GOVERNANCE or COMPLIANCE
# GOVERNANCE: Can be removed by users with permission
# COMPLIANCE: Cannot be removed until retention period expires

mc retention set --default GOVERNANCE "30d" myminio/immutable-backups

Set legal hold:

# Legal hold: Object cannot be deleted regardless of retention
mc legalhold set myminio/immutable-backups/important-file.txt

# Remove legal hold
mc legalhold clear myminio/immutable-backups/important-file.txt

8. Lifecycle Management

Automatic Object Expiration

Configure rules to automatically delete or transition objects.

Example lifecycle policy:

Create lifecycle.json:

{
  "Rules": [
    {
      "ID": "DeleteOldBackups",
      "Status": "Enabled",
      "Filter": {
        "Prefix": "daily-backups/"
      },
      "Expiration": {
        "Days": 30
      }
    },
    {
      "ID": "DeleteIncompleteUploads",
      "Status": "Enabled",
      "Filter": {},
      "AbortIncompleteMultipartUpload": {
        "DaysAfterInitiation": 7
      }
    },
    {
      "ID": "ExpireOldVersions",
      "Status": "Enabled",
      "Filter": {},
      "NoncurrentVersionExpiration": {
        "NoncurrentDays": 90
      }
    }
  ]
}

Apply lifecycle policy:

mc ilm import myminio/backups < lifecycle.json

# Verify
mc ilm ls myminio/backups

Remove lifecycle rules:

mc ilm rm --id="DeleteOldBackups" myminio/backups

9. Monitoring and Performance

MinIO Metrics

View server info:

mc admin info myminio

Monitor server usage:

# Real-time stats
mc admin top api myminio
mc admin top locks myminio
mc admin top disk myminio

Prometheus Integration

MinIO exports Prometheus metrics by default.

Metrics endpoint:

http://your-server:9000/minio/v2/metrics/cluster

Prometheus scrape config:

# prometheus.yml
scrape_configs:
  - job_name: 'minio'
    metrics_path: /minio/v2/metrics/cluster
    scheme: http
    static_configs:
      - targets: ['192.168.1.100:9000']
    basic_auth:
      username: 'prometheus'
      password: 'your-prometheus-password'

Create Prometheus user in MinIO:

mc admin user add myminio prometheus SecurePrometheusPassword
mc admin policy attach myminio diagnostics --user prometheus

Grafana Dashboard

Import MinIO dashboard:

  1. Grafana → Dashboards → Import
  2. Dashboard ID: 13502 (Official MinIO Dashboard)
  3. Select Prometheus data source
  4. Import

Metrics include:

  • Total storage usage
  • Request rates (GET, PUT, DELETE)
  • Error rates
  • Bandwidth utilization
  • Bucket statistics

Health Checks

# Liveness check (is MinIO running?)
curl http://localhost:9000/minio/health/live

# Readiness check (is MinIO ready for requests?)
curl http://localhost:9000/minio/health/ready

# Cluster health
mc admin heal myminio

10. Backup and Disaster Recovery

Backing Up MinIO Configuration

Export configuration:

# Export all config
mc admin config export myminio > minio-config-backup.txt

# Save to secure location
gpg --encrypt --recipient [email protected] minio-config-backup.txt

Export policies and users:

# List and backup all users
mc admin user list myminio > users-backup.txt

# List all policies
mc admin policy list myminio > policies-backup.txt

Backing Up Data

Option 1: Mirror to another MinIO instance:

# Configure second MinIO instance
mc alias set minio-backup https://backup-server:9000 admin backup-password

# Continuous mirroring
mc mirror --watch myminio/backups minio-backup/backups-mirror

Option 2: Backup to filesystem:

# Sync bucket to local filesystem
mc mirror myminio/critical-data /mnt/backup/minio-mirror/

# Schedule with cron
0 2 * * * mc mirror myminio/critical-data /mnt/backup/minio-mirror/

Option 3: Use Restic to backup MinIO data directory:

# Backup MinIO data directory
restic backup /mnt/storage/minio-data   --exclude="*.tmp"   --tag minio-backup

Disaster Recovery

Full recovery procedure:

  1. Restore MinIO configuration:
# Reinstall MinIO
docker-compose up -d minio

# Import configuration
mc admin config import myminio < minio-config-backup.txt
mc admin service restart myminio
  1. Restore data:
# Option A: From mirror
mc mirror minio-backup/backups-mirror myminio/backups

# Option B: From filesystem backup
mc mirror /mnt/backup/minio-mirror/ myminio/critical-data
  1. Verify integrity:
mc admin heal --recursive myminio
mc ls --recursive myminio/backups

Conclusion

You now have a comprehensive MinIO deployment providing S3-compatible object storage for your homelab.

Key Takeaways:

  1. S3 Compatibility: Works with any S3-compatible tool or application
  2. Performance: High-throughput storage for modern workloads
  3. Flexibility: Single-node to distributed multi-drive setups
  4. Security: Encryption, access policies, and immutable backups
  5. Cost-Effective: No cloud storage fees, full control

Your Next Steps:

  • Integrate with backup solutions (Restic, Duplicati, Veeam)
  • Set up lifecycle policies for automatic management
  • Enable versioning for critical buckets
  • Configure monitoring and alerting
  • Document your bucket structure and policies
  • Test disaster recovery procedures

Related Guides:

  • Restic + Backrest: Modern Backup Strategy for Homelabs
  • Borg Backup + Vorta: Efficient Homelab Backups
  • Syncthing: Peer-to-Peer File Synchronization

Your homelab now has enterprise-grade object storage!


Guide Version: 1.0 Last Updated: December 2025 Estimated Read Time: 30-40 minutes Estimated Implementation Time: 45 minutes

Created for homelabbing.io - AI-Forward Homelab Resource Platform