DEV Community

Elder Fernandes
Elder Fernandes

Posted on Originally published at selfhoststack-8z4.pages.dev

Self-Hosted S3 Object Storage: Garage vs MinIO vs SeaweedFS for Production

Self-Hosted S3 Object Storage: Garage vs MinIO vs SeaweedFS for Production

AWS S3 is the industry standard for unstructured blob storage, but its pricing model—especially cross-region data transfer, egress bandwidth, and per-request API costs (GET/PUT)—can become punishing as your app scales.

If you host media uploads, database backups, ML datasets, or user artifacts, self-hosting an S3-compatible object store gives you:

  • Zero Egress Fees: Transfer terabytes across your private network or public internet without bandwidth surcharges.
  • Full S3 API Compatibility: Works out-of-the-box with AWS CLI, boto3, @aws-sdk/client-s3, Rclone, Next.js, and backup tools like Kopia / Restic.
  • Predictable Fixed Costs: A €5–€15/mo Hetzner VPS or storage box delivers hundreds of gigabytes of NVMe/SSD storage at fixed pricing.

Here is an architectural comparison and production Docker Compose setup for the top three open-source engines in 2026: Garage, MinIO, and SeaweedFS.


1. Feature & Architecture Comparison

Feature / Engine Garage S3 (Rust) MinIO (Go) SeaweedFS (Go)
Best For Multi-node geo-distributed clusters & low memory Single-node VPS or enterprise multi-tenant High-throughput billions of small files
RAM Footprint ~50MB – 150MB ~400MB – 1.5GB ~150MB – 500MB
License AGPLv3 AGPLv3 Apache 2.0
Web Console UI Third-party / CLI Built-in web dashboard Built-in admin UI
Erasure Coding CRDT-based replication Reed-Solomon Erasure Coding Chunk-based Erasure Coding
Multi-Datacenter Native WAN latency tolerance Requires MinIO Enterprise/Site Replication Native Volume Server replication

2. Production Docker Compose: Garage S3 + Caddy Reverse Proxy

Garage is written in Rust, consumes minimal RAM (<100MB), and handles low-bandwidth network partitions gracefully.

garage.toml configuration:

metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "sqlite"

replication_factor = 1

[rpc]
bind_addr = "[::]:3901"
secret = "generate_32_byte_random_hex_key_here"

[s3_api]
api_bind_addr = "[::]:3900"
s3_region = "garage"
root_domain = ".s3.yourdomain.com"

[s3_web]
bind_addr = "[::]:3902"
root_domain = ".web.yourdomain.com"
Enter fullscreen mode Exit fullscreen mode

docker-compose.yml:

version: "3.8"

networks:
  s3-net:
    driver: bridge

volumes:
  garage-meta:
  garage-data:
  caddy-data:
  caddy-config:

services:
  caddy:
    image: caddy:2-alpine
    container_name: s3-caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy-data:/data
      - caddy-config:/config
    networks:
      - s3-net

  garage:
    image: dxflrs/garage:v0.9.4
    container_name: garage-s3
    restart: unless-stopped
    volumes:
      - ./garage.toml:/etc/garage.toml:ro
      - garage-meta:/var/lib/garage/meta
      - garage-data:/var/lib/garage/data
    networks:
      - s3-net
Enter fullscreen mode Exit fullscreen mode

Caddyfile:

s3.yourdomain.com, *.s3.yourdomain.com {
    reverse_proxy garage:3900
}
Enter fullscreen mode Exit fullscreen mode

3. MinIO Setup with Web Console

If you require an intuitive web browser UI to manage buckets, access keys, and lifecycle policies:

version: "3.8"

services:
  minio:
    image: minio/minio:latest
    container_name: minio-server
    restart: unless-stopped
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: "admin_user"
      MINIO_ROOT_PASSWORD: "SuperSecretPassword123!"
      MINIO_BROWSER_REDIRECT_URL: "https://console-s3.yourdomain.com"
      MINIO_SERVER_URL: "https://s3.yourdomain.com"
    volumes:
      - /mnt/storage/minio:/data
    ports:
      - "9000:9000"  # S3 API Endpoint
      - "9001:9001"  # Web Console
Enter fullscreen mode Exit fullscreen mode

4. Connecting SDKs & Backup Tools

AWS CLI:

aws --endpoint-url https://s3.yourdomain.com s3 mb s3://my-backups
aws --endpoint-url https://s3.yourdomain.com s3 cp ./dump.sql.gz s3://my-backups/
Enter fullscreen mode Exit fullscreen mode

Node.js / TypeScript (@aws-sdk/client-s3):

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

const s3 = new S3Client({
  endpoint: "https://s3.yourdomain.com",
  region: "garage",
  credentials: {
    accessKeyId: process.env.S3_ACCESS_KEY!,
    secretAccessKey: process.env.S3_SECRET_KEY!,
  },
  forcePathStyle: true,
});
Enter fullscreen mode Exit fullscreen mode

5. Security & Offsite Disaster Recovery

  1. Volume Encryption (LUKS): Encrypt the underlying host disk if hosting sensitive customer data.
  2. Automated Bucket Mirroring (Rclone): Run a nightly cronjob syncing critical buckets to a secondary VPS or Backblaze B2:
   rclone sync garage:production-uploads b2:offsite-mirror --transfers 8
Enter fullscreen mode Exit fullscreen mode

Explore full docker-compose recipes and benchmarks on SelfHostStack.

Need curated, production-tested .env templates and battle-tested Compose configs? Check out the Self-Hosted Starter Stack Pack ($29).

Top comments (0)