325 lines
11 KiB
YAML
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:
|