docker-compose.storage.yml
Rama main · réplica de lectura del repositorio original.
# ============================================================
# FreeFrame - optional bundled object storage (Silo, a MinIO fork)
# ============================================================
# Usage:
# docker compose --env-file .env.prod \
# -f docker-compose.prod.yml -f docker-compose.storage.yml up -d
# (add -f docker-compose.images.yml and/or -f docker-compose.https.yml as
# you need them; use the same -f flags for every later command)
#
# FreeFrame has NO local-filesystem storage backend; it speaks only the S3 API.
# This file runs an S3 server on this host and creates the bucket. It is the
# same Silo setup as the commented-out block in docker-compose.prod.yml, which
# is left as it is for installs that already uncommented it.
#
# In .env.prod set:
# S3_STORAGE=minio
# S3_ENDPOINT=http://minio:9000
# S3_ACCESS_KEY / S3_SECRET_KEY = strong values
# S3_BUCKET = lowercase letters, digits and hyphens only (no underscores)
# S3_PUBLIC_ENDPOINT = the address the BROWSER uses to reach storage, because
# presigned URLs go to the browser. Either:
# * https://<STORAGE_DOMAIN> with STORAGE_ROUTED=true (Traefik routes
# STORAGE_DOMAIN to Silo over HTTPS; needs docker-compose.https.yml, a
# DNS record for STORAGE_DOMAIN, and ports 80 and 443 open), or
# * http://<this host>:9000 with STORAGE_PORT_BIND=0.0.0.0:9000 for an
# HTTP-only install (open port 9000 to the browsers that use FreeFrame).
# FRONTEND_URL must be the exact origin people browse to; the app sets the
# bucket's CORS rules from it and from CORS_ALLOW_ORIGINS, and Silo enforces
# them.
#
# Optional, all with safe defaults:
# STORAGE_ROUTED=false true: Traefik serves STORAGE_DOMAIN over HTTPS.
# false keeps Traefik away from Silo, so an
# HTTP-mode install never asks Let's Encrypt for
# a certificate.
# STORAGE_DOMAIN=storage.invalid e.g. storage.review.example.com
# STORAGE_PORT_BIND=127.0.0.1:9000 where the S3 port is published on the
# host. The console (9001) is never published.
#
# An existing miniodata volume from the old minio/minio image opens as is.
# ============================================================
services:
minio:
image: pgsty/silo:RELEASE.2026-09-16T00-00-00Z # maintained MinIO fork; minio/minio is no longer pullable (#461)
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${S3_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY}
volumes:
- miniodata:/data
ports:
- "${STORAGE_PORT_BIND:-127.0.0.1:9000}:9000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
labels:
- traefik.enable=${STORAGE_ROUTED:-false}
- traefik.http.routers.minio.rule=Host(`${STORAGE_DOMAIN:-storage.invalid}`)
- traefik.http.routers.minio.entrypoints=websecure
- traefik.http.routers.minio.tls.certresolver=letsencrypt
- traefik.http.services.minio.loadbalancer.server.port=9000
minio-init: # one-shot: create the bucket (idempotent)
image: pgsty/mc:RELEASE.2026-09-16T00-00-00Z
env_file:
- .env.prod # the S3_* vars below are read inside the container
depends_on:
minio:
condition: service_healthy
entrypoint: >
/bin/sh -c "
mc alias set local http://minio:9000 $${S3_ACCESS_KEY} $${S3_SECRET_KEY} &&
mc mb --ignore-existing local/$${S3_BUCKET}
"
api:
depends_on:
minio:
condition: service_healthy
worker:
depends_on:
minio:
condition: service_healthy
email_worker:
depends_on:
minio:
condition: service_healthy
maintenance_worker:
depends_on:
minio:
condition: service_healthy
beat:
depends_on:
minio:
condition: service_healthy
volumes:
miniodata: