Files
AI_Video_Factory/docker-compose.prod.yml
youbin 36f466ba11
Some checks failed
CI / Migrations, tests, build, and audit (push) Failing after 10m7s
CI / Production gate and container images (push) Successful in 20m35s
Release v1.0.0
2026-07-31 14:21:43 +08:00

325 lines
11 KiB
YAML

name: ${COMPOSE_PROJECT_NAME:-frameflow-prod}
x-server-image: &server-image
image: frameflow-server:${FRAMEFLOW_VERSION:-latest}
build:
context: .
dockerfile: Dockerfile.server
x-server-environment: &server-environment
NODE_ENV: production
API_HOST: 0.0.0.0
API_PORT: 8787
API_PUBLIC_ORIGIN: ${API_PUBLIC_ORIGIN:?Set API_PUBLIC_ORIGIN}
WEB_ORIGIN: ${WEB_ORIGIN:?Set WEB_ORIGIN}
LOG_LEVEL: ${LOG_LEVEL:-info}
METRICS_HOST: 0.0.0.0
WORKER_METRICS_PORT: 9092
WORKER_HEARTBEAT_KEY: ${WORKER_HEARTBEAT_KEY:-frameflow:worker:heartbeat}
WORKER_HEARTBEAT_INTERVAL_MS: ${WORKER_HEARTBEAT_INTERVAL_MS:-5000}
WORKER_HEARTBEAT_TTL_SECONDS: ${WORKER_HEARTBEAT_TTL_SECONDS:-20}
METRICS_TOKEN: ${METRICS_TOKEN:-}
DATABASE_URL: ${DATABASE_URL:?Set DATABASE_URL}
REDIS_URL: ${REDIS_URL:?Set REDIS_URL}
JWT_ACCESS_SECRET: ${JWT_ACCESS_SECRET:?Set JWT_ACCESS_SECRET}
JWT_REFRESH_SECRET: ${JWT_REFRESH_SECRET:?Set JWT_REFRESH_SECRET}
CREDENTIAL_ENCRYPTION_KEY: ${CREDENTIAL_ENCRYPTION_KEY:?Set CREDENTIAL_ENCRYPTION_KEY}
ACCESS_TOKEN_TTL: ${ACCESS_TOKEN_TTL:-15m}
REFRESH_TOKEN_TTL_DAYS: ${REFRESH_TOKEN_TTL_DAYS:-30}
EMAIL_VERIFICATION_REQUIRED: ${EMAIL_VERIFICATION_REQUIRED:-true}
EMAIL_VERIFICATION_TTL_HOURS: ${EMAIL_VERIFICATION_TTL_HOURS:-24}
PASSWORD_RESET_TTL_MINUTES: ${PASSWORD_RESET_TTL_MINUTES:-30}
RESEND_API_KEY: ${RESEND_API_KEY:?Set RESEND_API_KEY}
EMAIL_FROM: ${EMAIL_FROM:?Set EMAIL_FROM}
S3_ENDPOINT: http://minio:9000
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT:?Set S3_PUBLIC_ENDPOINT}
S3_REGION: ${S3_REGION:-us-east-1}
S3_BUCKET: ${S3_BUCKET:-frameflow}
S3_ACCESS_KEY: ${S3_ACCESS_KEY:?Set S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY:?Set S3_SECRET_KEY}
S3_FORCE_PATH_STYLE: "true"
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
OPENAI_TEXT_MODEL: ${OPENAI_TEXT_MODEL:-gpt-5.6-sol}
OPENAI_IMAGE_MODEL: ${OPENAI_IMAGE_MODEL:-gpt-image-2}
OPENAI_TTS_MODEL: ${OPENAI_TTS_MODEL:-gpt-4o-mini-tts}
OPENAI_TTS_VOICE: ${OPENAI_TTS_VOICE:-alloy}
AGNES_API_KEY: ${AGNES_API_KEY:-}
AGNES_TEXT_MODEL: ${AGNES_TEXT_MODEL:-agnes-2.5-flash}
AGNES_IMAGE_MODEL: ${AGNES_IMAGE_MODEL:-agnes-image-2.1-flash}
AGNES_VIDEO_MODEL: ${AGNES_VIDEO_MODEL:-agnes-video-v2.0}
AGNES_VIDEO_PREDICTION_DEADLINE_SECONDS: ${AGNES_VIDEO_PREDICTION_DEADLINE_SECONDS:-900}
AGNES_VIDEO_POLL_INTERVAL_MS: ${AGNES_VIDEO_POLL_INTERVAL_MS:-4000}
GENERIC_TEXT_API_KEY: ${GENERIC_TEXT_API_KEY:-}
GENERIC_TEXT_BASE_URL: ${GENERIC_TEXT_BASE_URL:-}
GENERIC_TEXT_MODEL: ${GENERIC_TEXT_MODEL:-}
GENERIC_IMAGE_API_KEY: ${GENERIC_IMAGE_API_KEY:-}
GENERIC_IMAGE_BASE_URL: ${GENERIC_IMAGE_BASE_URL:-}
GENERIC_IMAGE_MODEL: ${GENERIC_IMAGE_MODEL:-}
GENERIC_VIDEO_API_KEY: ${GENERIC_VIDEO_API_KEY:-}
GENERIC_VIDEO_BASE_URL: ${GENERIC_VIDEO_BASE_URL:-}
GENERIC_VIDEO_MODEL: ${GENERIC_VIDEO_MODEL:-}
GENERIC_VIDEO_PREDICTION_DEADLINE_SECONDS: ${GENERIC_VIDEO_PREDICTION_DEADLINE_SECONDS:-900}
GENERIC_VIDEO_POLL_INTERVAL_MS: ${GENERIC_VIDEO_POLL_INTERVAL_MS:-4000}
REPLICATE_API_TOKEN: ${REPLICATE_API_TOKEN:-}
REPLICATE_IMAGE_VERSION: ${REPLICATE_IMAGE_VERSION:-}
REPLICATE_VIDEO_VERSION: ${REPLICATE_VIDEO_VERSION:-}
REPLICATE_LIPSYNC_VERSION: ${REPLICATE_LIPSYNC_VERSION:-}
REPLICATE_PREDICTION_DEADLINE_SECONDS: ${REPLICATE_PREDICTION_DEADLINE_SECONDS:-900}
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY:-}
STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET:-}
STRIPE_PRO_PRICE_ID: ${STRIPE_PRO_PRICE_ID:-}
STRIPE_STUDIO_PRICE_ID: ${STRIPE_STUDIO_PRICE_ID:-}
STRIPE_PORTAL_CONFIGURATION_ID: ${STRIPE_PORTAL_CONFIGURATION_ID:-}
BILLING_SUCCESS_URL: ${BILLING_SUCCESS_URL:-}
BILLING_CANCEL_URL: ${BILLING_CANCEL_URL:-}
PUBLISHING_CONNECTOR_URL: ${PUBLISHING_CONNECTOR_URL:-}
PUBLISHING_CONNECTOR_SECRET: ${PUBLISHING_CONNECTOR_SECRET:-}
PUBLISHING_CONNECTOR_TIMEOUT_MS: ${PUBLISHING_CONNECTOR_TIMEOUT_MS:-30000}
FFMPEG_PATH: ffmpeg
FFPROBE_PATH: ffprobe
WORKER_CONCURRENCY: ${WORKER_CONCURRENCY:-2}
MAX_UPLOAD_BYTES: ${MAX_UPLOAD_BYTES:-104857600}
PROVIDER_DOWNLOAD_TIMEOUT_MS: ${PROVIDER_DOWNLOAD_TIMEOUT_MS:-180000}
PROVIDER_DOWNLOAD_MAX_REDIRECTS: ${PROVIDER_DOWNLOAD_MAX_REDIRECTS:-3}
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB:-frameflow}
POSTGRES_USER: ${POSTGRES_USER:-frameflow}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 20
restart: unless-stopped
redis:
image: redis:7.4-alpine
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD:?Set REDIS_PASSWORD}
command: ["redis-server", "--appendonly", "yes", "--requirepass", "${REDIS_PASSWORD:?Set REDIS_PASSWORD}"]
volumes:
- redis_data:/data
healthcheck:
test: ["CMD-SHELL", "redis-cli -a \"$${REDIS_PASSWORD}\" ping | grep -q PONG"]
interval: 5s
timeout: 5s
retries: 20
restart: unless-stopped
minio:
image: minio/minio@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${S3_ACCESS_KEY:?Set S3_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY:?Set S3_SECRET_KEY}
MINIO_API_CORS_ALLOW_ORIGIN: ${WEB_ORIGIN:?Set WEB_ORIGIN}
MINIO_PROMETHEUS_AUTH_TYPE: public
volumes:
- minio_data:/data
ports:
- "${MINIO_CONSOLE_BIND:-127.0.0.1}:${MINIO_CONSOLE_PORT:-9001}:9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:9000/minio/health/live"]
interval: 5s
timeout: 5s
retries: 20
restart: unless-stopped
minio-init:
image: minio/mc@sha256:a7fe349ef4bd8521fb8497f55c6042871b2ae640607cf99d9bede5e9bdf11727
depends_on:
minio:
condition: service_healthy
environment:
S3_BUCKET: ${S3_BUCKET:-frameflow}
S3_ACCESS_KEY: ${S3_ACCESS_KEY:?Set S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY:?Set S3_SECRET_KEY}
entrypoint: ["/bin/sh", "-c"]
command:
- >-
mc alias set local http://minio:9000 "$${S3_ACCESS_KEY}" "$${S3_SECRET_KEY}" &&
mc mb --ignore-existing "local/$${S3_BUCKET}" &&
mc anonymous set none "local/$${S3_BUCKET}"
restart: "no"
config-check:
<<: *server-image
environment: *server-environment
command: ["node", "server/dist/config-check.js"]
read_only: true
tmpfs:
- /tmp:size=16m
restart: "no"
migrate:
<<: *server-image
environment: *server-environment
command: ["node", "server/dist/db/migrate.js"]
depends_on:
config-check:
condition: service_completed_successfully
postgres:
condition: service_healthy
read_only: true
tmpfs:
- /tmp:size=128m
restart: "no"
api:
<<: *server-image
environment: *server-environment
command: ["node", "server/dist/index.js"]
depends_on:
migrate:
condition: service_completed_successfully
redis:
condition: service_healthy
minio-init:
condition: service_completed_successfully
read_only: true
tmpfs:
- /tmp:size=512m
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8787/health/ready').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
interval: 10s
timeout: 5s
start_period: 15s
retries: 10
restart: unless-stopped
worker:
<<: *server-image
environment: *server-environment
command: ["node", "server/dist/worker.js"]
depends_on:
migrate:
condition: service_completed_successfully
redis:
condition: service_healthy
minio-init:
condition: service_completed_successfully
read_only: true
tmpfs:
- /tmp:size=4g
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:9092/health/ready').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
interval: 15s
timeout: 5s
start_period: 15s
retries: 10
restart: unless-stopped
web:
image: frameflow-web:${FRAMEFLOW_VERSION:-latest}
build:
context: .
dockerfile: Dockerfile.web
args:
VITE_API_URL: /api/v1
environment:
APP_HOST: ${APP_HOST:?Set APP_HOST}
MEDIA_HOST: ${MEDIA_HOST:?Set MEDIA_HOST}
MAX_UPLOAD_SIZE: ${MAX_UPLOAD_SIZE:-100m}
ports:
- "${HTTP_BIND:-0.0.0.0}:${HTTP_PORT:-8080}:80"
depends_on:
api:
condition: service_healthy
minio:
condition: service_healthy
restart: unless-stopped
prometheus:
image: prom/prometheus:v3.5.0
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=${PROMETHEUS_RETENTION:-30d}
volumes:
- ./deploy/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./deploy/alerts.yml:/etc/prometheus/alerts.yml:ro
- prometheus_data:/prometheus
ports:
- "${PROMETHEUS_BIND:-127.0.0.1}:${PROMETHEUS_PORT:-9090}:9090"
depends_on:
api:
condition: service_healthy
worker:
condition: service_healthy
minio:
condition: service_healthy
node-exporter:
condition: service_healthy
alertmanager:
condition: service_healthy
restart: unless-stopped
node-exporter:
image: quay.io/prometheus/node-exporter:v1.9.1
command:
- --path.rootfs=/host
- --collector.textfile.directory=/textfile
pid: host
volumes:
- /:/host:ro
- ${FRAMEFLOW_BACKUP_METRICS_DIR:-/var/lib/frameflow/metrics}:/textfile:ro
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:9100/metrics"]
interval: 15s
timeout: 5s
start_period: 5s
retries: 10
restart: unless-stopped
alertmanager:
image: prom/alertmanager:v0.28.1
environment:
ALERTMANAGER_WEBHOOK_URL: ${ALERTMANAGER_WEBHOOK_URL:?Set ALERTMANAGER_WEBHOOK_URL}
entrypoint: ["/bin/sh", "-ec"]
command:
- |-
case "$${ALERTMANAGER_WEBHOOK_URL}" in
https://*) ;;
*) echo "ALERTMANAGER_WEBHOOK_URL must use HTTPS" >&2; exit 1 ;;
esac
escaped_url=$$(printf '%s' "$${ALERTMANAGER_WEBHOOK_URL}" | sed 's/[\\&|]/\\&/g')
sed "s|__ALERTMANAGER_WEBHOOK_URL__|$${escaped_url}|g" /etc/alertmanager/alertmanager.yml.template > /tmp/alertmanager.yml
/bin/amtool check-config /tmp/alertmanager.yml
exec /bin/alertmanager \
--config.file=/tmp/alertmanager.yml \
--storage.path=/alertmanager \
--enable-feature=receiver-name-in-metrics
volumes:
- ./deploy/alertmanager.yml.template:/etc/alertmanager/alertmanager.yml.template:ro
- alertmanager_data:/alertmanager
ports:
- "${ALERTMANAGER_BIND:-127.0.0.1}:${ALERTMANAGER_PORT:-9093}:9093"
read_only: true
tmpfs:
- /tmp:size=16m
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:9093/-/ready"]
interval: 10s
timeout: 5s
start_period: 5s
retries: 10
restart: unless-stopped
volumes:
postgres_data:
redis_data:
minio_data:
prometheus_data:
alertmanager_data: