Run MinIO with Docker and Caddy: Secure S3-Compatible Object Storage on Ubuntu 24.04

Overview

This step-by-step guide shows how to deploy MinIO with Docker and secure it behind Caddy for automatic HTTPS. MinIO is a high-performance, S3-compatible object storage that you can self-host for backups, logs, media, and AI datasets. We will use Docker Compose on Ubuntu 24.04, set up Caddy as a reverse proxy with free Let’s Encrypt TLS, create a bucket, and test access using the AWS CLI. The result is a production-ready, S3-compatible endpoint at your own domain.

Prerequisites

You will need: (1) a clean Ubuntu 24.04 server with a public IP, (2) a domain with two DNS A records pointing to your server (for example, minio.example.com and console.example.com), (3) Docker and Docker Compose installed, and (4) port 80/443 open in your firewall and cloud security group. Replace example.com with your domain throughout this tutorial.

Step 1: Open firewall and prepare project

Ensure inbound HTTP/HTTPS traffic is allowed so Caddy can obtain and renew TLS certificates. On Ubuntu with UFW:

sudo ufw allow 80,443/tcp
sudo ufw reload
mkdir -p ~/minio-caddy && cd ~/minio-caddy

Step 2: Create Docker Compose file

We will run MinIO and Caddy on the same Docker network. Caddy will request certificates from Let’s Encrypt automatically and reverse proxy to MinIO’s API and web console.

cat > docker-compose.yml <<'YAML'
version: "3.8"
services:
  minio:
    image: minio/minio:latest
    command: server /data --console-address ":9001" --address ":9000"
    environment:
      - MINIO_ROOT_USER=admin
      - MINIO_ROOT_PASSWORD=ChangeMe-StrongSecret123!
    volumes:
      - minio_data:/data
    restart: unless-stopped
    networks:
      - edge

  caddy:
    image: caddy:2
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
    depends_on:
      - minio
    restart: unless-stopped
    networks:
      - edge

volumes:
  minio_data:

networks:
  edge:
    driver: bridge
YAML

Step 3: Create Caddyfile for automatic HTTPS

This configuration terminates TLS, enables compression, and proxies API and console to MinIO. Make sure both hostnames have valid DNS A records pointing to your server’s public IP before starting.

cat > Caddyfile <<'CADDY'
minio.example.com {
  encode zstd gzip
  reverse_proxy minio:9000
}

console.example.com {
  encode zstd gzip
  reverse_proxy minio:9001
}
CADDY

Step 4: Start the stack

Bring everything up in the background. Caddy will automatically request TLS certificates from Let’s Encrypt on the first request and keep them renewed.

docker compose up -d
docker compose logs -f caddy

Once ready, visit https://console.example.com to access the MinIO console. Log in with the root credentials you set (admin / ChangeMe-StrongSecret123!). For security, change this password after your first login and create dedicated users for apps instead of sharing root.

Step 5: Create a bucket and access keys with MinIO Client (mc)

Use the MinIO Client to create a bucket and a non-root user with read/write access. Running mc in Docker avoids installing additional packages on the host.

# Add the MinIO endpoint alias (uses HTTPS through Caddy)
docker run --rm -it minio/mc \
  alias set myminio https://minio.example.com admin 'ChangeMe-StrongSecret123!'

# Create a bucket
docker run --rm -it minio/mc mb myminio/my-bucket

# Create an app user (access key) and attach readwrite policy
docker run --rm -it minio/mc \
  admin user add myminio appuser 'Another-StrongSecret456!'

docker run --rm -it minio/mc \
  admin policy attach myminio readwrite --user appuser

Step 6: Test with AWS CLI

MinIO is S3-compatible, so existing tools work by pointing to your endpoint. The AWS CLI is a convenient way to confirm access.

# Install AWS CLI if missing (Ubuntu)
sudo apt-get update && sudo apt-get install -y awscli

# Export temporary credentials for testing
export AWS_ACCESS_KEY_ID=appuser
export AWS_SECRET_ACCESS_KEY='Another-StrongSecret456!'

# List buckets on MinIO via your HTTPS endpoint
aws s3 ls --endpoint-url https://minio.example.com

# Upload a file to the new bucket
echo "hello from minio" > test.txt
aws s3 cp test.txt s3://my-bucket/ --endpoint-url https://minio.example.com

Maintenance and hardening tips

- Change the root password after initial setup and keep it offline. Create per-application users with the least privileges required. Rotate secrets regularly.

- Back up the MinIO data volume and, if critical, replicate to another MinIO cluster or cloud S3 using lifecycle policies or tools like rclone. Test restores.

- Keep Docker images up to date: run “docker compose pull && docker compose up -d” during a maintenance window. MinIO releases frequent fixes and performance updates.

- Monitor health and logs: “docker compose logs -f minio” and “docker compose logs -f caddy”. Use MinIO Console dashboards to watch capacity and performance.

Troubleshooting

- Certificate errors: confirm DNS is correct and that port 80 is open. Let’s Encrypt requires HTTP-01 reachability for the first certificate. Check “docker compose logs -f caddy”.

- 502/Bad Gateway: ensure “minio” container is running and healthy, and verify the Caddyfile hostnames match your browser URL. Restart with “docker compose restart”.

- Permission or upload failures: verify bucket policies or user permissions via the MinIO Console, and confirm your AWS CLI command uses “--endpoint-url”.

You now have a secure, S3-compatible object storage endpoint running on your own infrastructure with automatic HTTPS. Use it for application artifacts, backups, and large datasets, and manage everything from an easy web console and standard S3 tooling.

Build Your Own Encrypted S3 Backup with Restic and MinIO (Docker Compose, 2025 Guide)

If you want fast, encrypted, and deduplicated backups without paying for a public cloud, pairing Restic with MinIO is a powerful option. Restic handles client-side encryption and incremental backups; MinIO provides an S3-compatible object store you fully control. In this hands-on guide, you will deploy MinIO using Docker Compose, initialize a Restic repository, automate backups with a retention policy, and verify restores. The steps work on modern Linux distributions (Ubuntu/Debian/RHEL) and on any host that can run Docker.

Prerequisites

- A Linux server with at least 2 CPU cores, 4 GB RAM, and storage sized to your backup needs.
- Docker and Docker Compose installed.
- A shell user with sudo rights.
- Optional but recommended: a hostname and TLS if exposing MinIO beyond localhost.

Step 1 — Create a Docker Compose file for MinIO

We will run MinIO on ports 9000 (S3 API) and 9001 (web console). Use a strong root user and password. Put the credentials into a .env file so they are not hard-coded in the Compose file.

# .env
MINIO_ROOT_USER=minioadmin_change_me_now
MINIO_ROOT_PASSWORD=SuperStrong_Passw0rd_change_me
MINIO_DATA_PATH=./minio-data
# docker-compose.yml
services:
  minio:
    image: minio/minio:latest
    container_name: minio
    command: server /data --console-address ":9001"
    ports:
      - "9000:9000"   # S3 API
      - "9001:9001"   # Web Console
    environment:
      - MINIO_ROOT_USER=${MINIO_ROOT_USER}
      - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
    volumes:
      - ${MINIO_DATA_PATH}:/data
    restart: unless-stopped

Start MinIO:

docker compose up -d

Open the web console at http://<server-ip>:9001 and sign in with the root user credentials from the .env file.

Step 2 — Create a bucket and a dedicated access key

For clear separation, create a bucket named restic and generate a dedicated access key limited to that bucket.

Option A (Web Console):

- Storage → Create bucket → Name: restic.
- Identity → Service Accounts → Create access key → Scope: restrict to the restic bucket (read/write). Save the Access Key and Secret Key.

Option B (CLI, quick start):

# Uses MinIO Client without permanent install
docker run --rm --network host \
  -e MC_HOST_local="http://${MINIO_ROOT_USER}:${MINIO_ROOT_PASSWORD}@127.0.0.1:9000" \
  minio/mc mb local/restic

For production, prefer a limited service account rather than using the root keys in automation.

Step 3 — Initialize a Restic repository

Install Restic from your distribution or from the official releases. Then export environment variables for the repository URL and credentials. Replace placeholders with your actual keys.

export RESTIC_REPOSITORY="s3:http://127.0.0.1:9000/restic"
export AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="YOUR_SECRET_KEY"
export RESTIC_PASSWORD="Choose_A_Strong_Repo_Password"

Initialize the repository (first time only):

restic init

Restic encrypts data with the password you set, before uploading. Keep this password safe; without it, restores are impossible.

Step 4 — Create a backup script with retention

Let’s script a daily backup with sensible retention (7 daily, 4 weekly, 6 monthly) and basic integrity checks.

sudo tee /usr/local/bin/restic-backup.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

# Environment (consider moving to /etc/restic/.env and source it)
export RESTIC_REPOSITORY="s3:http://127.0.0.1:9000/restic"
export AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="YOUR_SECRET_KEY"
export RESTIC_PASSWORD="Choose_A_Strong_Repo_Password"

# What to back up
INCLUDE_DIRS=(
  "/etc"
  "/var/www"
  "/var/lib/docker/volumes/project_data/_data"
  "$HOME"
)

# Exclusions
EXCLUDES=( 
  "--exclude-file=/etc/restic-excludes.txt"
)

# Run backup
restic backup "${EXCLUDES[@]}" "${INCLUDE_DIRS[@]}"

# Retention and pruning
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

# Lightweight consistency check (5% of data)
restic check --read-data-subset=5%
EOF
sudo chmod +x /usr/local/bin/restic-backup.sh

Create a simple exclusions file to skip caches and temporary data:

sudo tee /etc/restic-excludes.txt >/dev/null <<'EOF'
/home/*/.cache
/var/cache
/var/tmp
*.iso
*.qcow2
node_modules
EOF

Schedule the backup with cron:

(crontab -l 2>/dev/null; echo "0 2 * * * /usr/local/bin/restic-backup.sh >> /var/log/restic.log 2>&1") | crontab -

Tip: For long-running servers, a systemd timer is often more reliable than cron. Also consider moving credentials into a protected file (chmod 600) and sourcing it inside the script.

Step 5 — Test listing and restoring

Verify snapshots:

restic snapshots

Restore a file or directory to a safe location:

# Find a snapshot ID from 'restic snapshots'
restic restore latest --target "$HOME/restore-test" --include /etc/hosts

Always test a restore. A backup you cannot restore is not a backup.

Step 6 — Secure access and expose safely

- Use HTTPS for MinIO. Either terminate TLS directly in MinIO (via certificates at /data/.minio/certs) or put MinIO behind a reverse proxy like Caddy or Nginx with automatic Let’s Encrypt.
- If backing up from remote machines, prefer private networks (Tailscale/WireGuard) rather than exposing port 9000 to the internet.
- Set strong passwords and rotate access keys periodically.

Step 7 — Useful maintenance and troubleshooting

- Verify integrity periodically: restic check (run monthly with full read if you can).
- Remove stale locks: restic unlock if a job was interrupted.
- Monitor logs: tail -f /var/log/restic.log for failures or timeouts.
- Storage growth: run restic prune occasionally (already included with forget --prune) and review your exclude list.

Why this stack works in 2025

Restic remains one of the most efficient open-source backup tools thanks to encryption-by-default, deduplication, and built-in S3 support. MinIO provides a modern, high-performance, and S3-compatible backend you can run on-prem or at the edge. With Docker Compose, you can upgrade easily, keep state on mapped volumes, and move the stack between servers without changing your backup commands.

With the steps above, you now have a repeatable, scriptable, and secure backup workflow: data is encrypted before it leaves the host, stored in an S3 bucket you control, and cleaned up automatically with a clear retention policy. Don’t forget to document your RESTIC_PASSWORD location and your MinIO access keys, and test restores on a schedule.

Popular Posts

Install Ollama and Open WebUI on Ubuntu 24.04 with NVIDIA GPU Acceleration (Step-by-Step)

Install Ollama + Open WebUI on Ubuntu 24.04 with NVIDIA GPU Acceleration (Step-by-Step)

Install a Local AI Chatbot on Ubuntu 24.04 with Ollama and Open WebUI (Step-by-Step)

Trending Now

Recovering from Btrfs Boot Failures Using GUI Tools on Fedora

By the end of this guide the reader will be able to identify a Btrfs‑based Fedora installation, boot from a live USB, list and restore snapshots using the graphical utilities btrfs‑assistant and snapper, and verify that the system returns to a functional state without resorting to the command line. Understanding the Btrfs Layout Used by Fedora Fedora Workstation and Fedora KDE install the root filesystem as a single Btrfs partition that contains two default sub‑volumes. One sub‑volume holds the traditional “/” hierarchy, while the second is dedicated to /var/lib/machines . The latter exists to keep container images out of snapshot operations; it remains empty on systems that do not run virtual machines. Because Btrfs stores data in sub‑volumes rather than separate partitions, a snapshot captures the state of an entire sub‑volume at a point in time. The installer (Anaconda) automatically registers these sub‑volumes with the snapper service. Snapper maintains a series of read‑only ...