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"
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
Caddyfile:
s3.yourdomain.com, *.s3.yourdomain.com {
reverse_proxy garage:3900
}
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
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/
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,
});
5. Security & Offsite Disaster Recovery
- Volume Encryption (LUKS): Encrypt the underlying host disk if hosting sensitive customer data.
- 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
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)