compose.yaml
Rama main · réplica de lectura del repositorio original.
name: mediahub
x-logging: &logging
driver: json-file
options:
max-size: "10m"
max-file: "5"
x-backend-env: &backend-env
MEDIAHUB_DATABASE_URL: postgresql+psycopg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
MEDIAHUB_STORAGE_ROOT: /data/storage
MEDIAHUB_ENVIRONMENT: ${MEDIAHUB_ENVIRONMENT:-production}
MEDIAHUB_TIMEZONE: ${MEDIAHUB_TIMEZONE:-Asia/Taipei}
MEDIAHUB_MAX_UPLOAD_BYTES: ${MEDIAHUB_MAX_UPLOAD_BYTES:-21474836480}
MEDIAHUB_MIN_FREE_BYTES: ${MEDIAHUB_MIN_FREE_BYTES:-2147483648}
MEDIAHUB_FFMPEG_THREADS: ${MEDIAHUB_FFMPEG_THREADS:-4}
MEDIAHUB_MASTER_KEY: ${MEDIAHUB_MASTER_KEY:?run scripts/init-env.sh (MEDIAHUB_MASTER_KEY missing)}
MEDIAHUB_ALLOW_SIMULATED_NODES: ${MEDIAHUB_ALLOW_SIMULATED_NODES:-false}
MEDIAHUB_PUBLIC_URL: ${MEDIAHUB_PUBLIC_URL:-}
MEDIAHUB_NODE_OFFLINE_AFTER_SECONDS: ${MEDIAHUB_NODE_OFFLINE_AFTER_SECONDS:-90}
MEDIAHUB_NODE_HEARTBEAT_SECONDS: ${MEDIAHUB_NODE_HEARTBEAT_SECONDS:-30}
TZ: ${MEDIAHUB_TIMEZONE:-Asia/Taipei}
services:
db:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 20
logging: *logging
api:
build:
context: ./backend
target: base
image: mediahub-api:${HUB_IMAGE_TAG:-latest}
restart: unless-stopped
environment: *backend-env
volumes:
- storage:/data/storage
# Public root CA certificate only (exported by the caddy container) — lets pairing
# payloads carry the CA fingerprint for pinning. Caddy's private keys stay in caddy_data.
- ca_public:/ca-public:ro
# Android storage-node app, built on the host (scripts/build-android.sh), served to admins.
- ./android/dist:/app/android-dist:ro
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/health', timeout=4)"]
interval: 10s
timeout: 5s
retries: 12
start_period: 20s
mem_limit: 1g
logging: *logging
worker:
image: mediahub-api:${HUB_IMAGE_TAG:-latest}
restart: unless-stopped
command: ["python", "-m", "app.worker"]
environment: *backend-env
volumes:
- storage:/data/storage
depends_on:
api:
condition: service_healthy
# Transcoding is CPU-heavy: cap it so the rest of the machine stays responsive.
cpus: ${MEDIAHUB_WORKER_CPUS:-4}
mem_limit: 3g
stop_grace_period: 40s
logging: *logging
caddy:
build:
context: .
dockerfile: deploy/caddy/Dockerfile
image: mediahub-web:${HUB_IMAGE_TAG:-latest}
restart: unless-stopped
environment:
HUB_SITES: ${HUB_SITES:-https://localhost}
HUB_DEFAULT_SNI: ${HUB_DEFAULT_SNI:-localhost}
HUB_MAX_UPLOAD: ${HUB_MAX_UPLOAD:-20GB}
ports:
# Docker-published ports bypass ufw. Default binds to loopback only; set
# HUB_BIND to this machine's LAN IP to serve phones/PCs on the same network.
- "${HUB_BIND:-127.0.0.1}:${HUB_HTTPS_PORT:-443}:443"
volumes:
- caddy_data:/data
- caddy_config:/config
- ca_public:/ca-public
depends_on:
api:
condition: service_healthy
logging: *logging
# `docker compose --profile test run --rm tests` — isolated DB + temp storage.
tests:
profiles: ["test"]
build:
context: ./backend
target: test
image: mediahub-api:test
environment:
MEDIAHUB_DATABASE_URL: postgresql+psycopg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/mediahub_test
MEDIAHUB_STORAGE_ROOT: /tmp/mediahub-test
MEDIAHUB_ENVIRONMENT: test
MEDIAHUB_COOKIE_SECURE: "false"
MEDIAHUB_ALLOW_SIMULATED_NODES: "true"
depends_on:
db:
condition: service_healthy
volumes:
pgdata:
storage:
caddy_data:
caddy_config:
ca_public: