Self-hosted S3-compatible object storage for backups: MinIO behind an Nginx proxy, with an rclone-based backup agent and versioned buckets. Your offsite backups without a cloud bill.
A complete Kubernetes lab-to-production pattern: k3s in Docker with the embedded etcd datastore, Rancher for management, MetalLB for Layer-2 LoadBalancer IPs, Longhorn replicated storage, and Traefik
When one node is not enough: SeaweedFS provides distributed, replicated S3 storage with a master/filer/volume architecture — ideal as a large-scale backup target on commodity hardware.
A deployment guide with working examples — not a downloadable product. Everything below is the actual configuration I use, abridged to the parts that matter.
services:
seaweedfs:
image: chrislusf/seaweedfs:${SEAWEEDFS_VERSION:-4.47}
container_name: seaweedfs-server
restart: unless-stopped
entrypoint:
- /bin/sh
- -c
command:
- >
# SeaweedFS S3 IAM config (users/keys) generated from env
printf
'{"identies":[{"name":"admin","credentials":[{"accessKey":"%s","secretKey":"%s"}],"actions":["Admin"]}]}'
\
"$$S3_ACCESS_KEY" "$$S3_SECRET_KEY" > /tmp/s3.json
# Single binary: master + volume + filer + S3 gateway
exec weed server \
-dir=/data \
-ip=seaweedfs \
-master.volumeSizeLimitMB=1024 \
-volume.max=0 \
-master \
-volume \
-filer \
-filer.defaultReplicaPlacement=000 \
-s3 -s3.config=/tmp/s3.json -s3.port=8333
ports:
- ${S3_API_PORT:-9000}:8333
- ${MASTER_UI_PORT:-9333}:9333
environment:
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-admin}
S3_SECRET_KEY: ${S3_SECRET_KEY:-change-me}
volumes:
- weed-data:/data
healthcheck:
test:
- CMD
- wget
- -q
- --spider
- http://localhost:9333/cluster/status
interval: 15s
timeout: 5s
retries: 5
start_period: 10s
networks:
- storage
backup-agent:
image: rclone/rclone:${RCLONE_VERSION:-1.75.1}
container_name: seaweedfs-backup
restart: unless-stopped
entrypoint:
- /bin/sh
- -c
command:
- |
# Local alias via env vars; remote alias only if configured
if [ -n "$$BACKUP_ENDPOINT" ]; then
rclone config create remote s3 provider=Other \
endpoint="$$BACKUP_ENDPOINT" \
access_key_id="$$BACKUP_ACCESS_KEY" \
secret_key="$$BACKUP_SECRET_KEY" >/dev/null
fi
while true; do
echo "[$$] Running backup at $$(date)"
sh /config/backup.sh 2>&1 || echo "Backup failed"
sleep $$BACKUP_INTERVAL
done
environment:
RCLONE_CONFIG_LOCAL_TYPE: s3
RCLONE_CONFIG_LOCAL_PROVIDER: Other
RCLONE_CONFIG_LOCAL_ENDPOINT: http://seaweedfs:8333
RCLONE_CONFIG_LOCAL_ACCESS_KEY_ID: ${S3_ACCESS_KEY:-admin}
RCLONE_CONFIG_LOCAL_SECRET_ACCESS_KEY: ${S3_SECRET_KEY:-change-me}
volumes:
- ./config:/config:ro
depends_on:
seaweedfs:
condition: service_healthy
networks:
- storage
Excerpt — seaweedfs, backup-agent from the compose file. The remaining services (proxies, init jobs, exporters) follow the same pattern and mount their configuration from a config/ directory.
Copy .env.example to .env and at minimum set:
SEAWEEDFS_VERSION=4.47
S3_API_PORT=9000
MASTER_UI_PORT=9333
S3_ACCESS_KEY=admin
S3_SECRET_KEY=change-me
RCLONE_VERSION=1.75.1
.env before the first start — never ship the example valuesA guide, not a product. This page is deployment documentation with working examples — there is no zip, no download, no support contract. You adapt the patterns to your own environment, and you own the result.
Want this running production-grade in your infrastructure instead? Scaling storage like this is part of my infrastructure consulting — details on the consulting page.