# Phase 7 studio pack. Not a production IdP. No secrets in this file. # # docker compose -f docker-compose.studio.yml up --build # # Ports (published on the host loopback only): # studio-api 127.0.0.1:8000 FastAPI + in-process thread worker (default) # studio-web 127.0.0.1:5174 nginx SPA; /api proxies to studio-api # # The API listens on all container interfaces so studio-web can reach it. # That listen is opt-in (STUDIO_ALLOW_NON_LOOPBACK) and requires # STUDIO_API_TOKEN from the environment. The image does not bake a token. # The retired default token is refused. # studio-web does not write studio-token.js. Nginx adds Authorization on /api # for Host 127.0.0.1, localhost, and [::1], and returns 444 for any other Host. # It clears X-User-Name and X-Forwarded-User. # # Optional profiles (not started by default): # --profile worker standalone thread poller. Set STUDIO_JOB_WORKER=off on the API first. # --profile postgres Postgres instead of the SQLite volume. Point STUDIO_DATABASE_URL at it. # --profile minio local object store. Set STUDIO_MEDIA_BACKEND=s3 + bucket/endpoint + credentials # in the *runtime environment* (do not commit them). Unset → local disk, honestly. # --profile celery Redis + Celery worker. Set STUDIO_JOB_WORKER=celery on the API. # Never run the thread worker and this Celery worker against the same SQLite file. # Prefer --profile postgres when using Celery. # # OIDC is env-only (STUDIO_AUTH_MODE=oidc + issuer + audience). This file does not # enable it. Placeholders only — do not commit client secrets. services: studio-api: build: context: ./studio ports: - "127.0.0.1:8000:8000" environment: STUDIO_DATABASE_URL: ${STUDIO_DATABASE_URL:-sqlite:////data/studio.db} STUDIO_MEDIA_ROOT: /data/media STUDIO_MEDIA_BACKEND: ${STUDIO_MEDIA_BACKEND:-local} STUDIO_S3_BUCKET: ${STUDIO_S3_BUCKET:-} STUDIO_S3_ENDPOINT: ${STUDIO_S3_ENDPOINT:-} STUDIO_S3_PREFIX: ${STUDIO_S3_PREFIX:-} STUDIO_S3_REGION: ${STUDIO_S3_REGION:-us-east-1} STUDIO_BIND_HOST: 0.0.0.0 STUDIO_ALLOW_NON_LOOPBACK: "1" STUDIO_API_TOKEN: "${STUDIO_API_TOKEN:?Set STUDIO_API_TOKEN to a secret you choose}" STUDIO_DEFAULT_USER: ${STUDIO_DEFAULT_USER:-local-dev} STUDIO_PACK_BUILDER_URL: ${STUDIO_PACK_BUILDER_URL:-http://localhost:5173} STUDIO_CORS_ORIGINS: ${STUDIO_CORS_ORIGINS:-http://localhost:5174,http://127.0.0.1:5174,http://localhost:8000} STUDIO_JOB_WORKER: ${STUDIO_JOB_WORKER:-thread} STUDIO_CELERY_BROKER_URL: ${STUDIO_CELERY_BROKER_URL:-} STUDIO_AUTH_MODE: ${STUDIO_AUTH_MODE:-local} STUDIO_OIDC_ISSUER: ${STUDIO_OIDC_ISSUER:-} STUDIO_OIDC_AUDIENCE: ${STUDIO_OIDC_AUDIENCE:-} STUDIO_OIDC_CLIENT_ID: ${STUDIO_OIDC_CLIENT_ID:-} STUDIO_OIDC_JWKS_URL: ${STUDIO_OIDC_JWKS_URL:-} STUDIO_OIDC_NAME_CLAIM: ${STUDIO_OIDC_NAME_CLAIM:-preferred_username} STUDIO_OIDC_ROLE_CLAIM: ${STUDIO_OIDC_ROLE_CLAIM:-} STUDIO_OIDC_ROLE_MAP: ${STUDIO_OIDC_ROLE_MAP:-} STUDIO_OIDC_APPLY_ROLE_CLAIM: ${STUDIO_OIDC_APPLY_ROLE_CLAIM:-false} STUDIO_NOTIFY_WEBHOOK_URL: ${STUDIO_NOTIFY_WEBHOOK_URL:-} STUDIO_STILL_ADAPTER: ${STUDIO_STILL_ADAPTER:-stub} STUDIO_CLIP_ADAPTER: ${STUDIO_CLIP_ADAPTER:-stub} STUDIO_COMFY_STILL_LANES: ${STUDIO_COMFY_STILL_LANES:-} STUDIO_COMFY_CLIP_LANES: ${STUDIO_COMFY_CLIP_LANES:-} STUDIO_COMFY_IMAGE_LANES_FOR_FREE: ${STUDIO_COMFY_IMAGE_LANES_FOR_FREE:-} STUDIO_COMFY_ALLOW_HOSTS: ${STUDIO_COMFY_ALLOW_HOSTS:-} STUDIO_COMFY_H3_STYLES: ${STUDIO_COMFY_H3_STYLES:-} volumes: - studio-data:/data healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"] interval: 10s timeout: 3s retries: 8 studio-web: build: context: ./studio-web ports: - "127.0.0.1:5174:80" environment: STUDIO_API_TOKEN: "${STUDIO_API_TOKEN:?Set STUDIO_API_TOKEN to a secret you choose}" depends_on: studio-api: condition: service_healthy studio-worker: profiles: ["worker"] build: context: ./studio command: ["python", "-m", "app.jobs"] environment: STUDIO_DATABASE_URL: ${STUDIO_DATABASE_URL:-sqlite:////data/studio.db} STUDIO_MEDIA_ROOT: /data/media STUDIO_MEDIA_BACKEND: ${STUDIO_MEDIA_BACKEND:-local} STUDIO_S3_BUCKET: ${STUDIO_S3_BUCKET:-} STUDIO_S3_ENDPOINT: ${STUDIO_S3_ENDPOINT:-} STUDIO_S3_PREFIX: ${STUDIO_S3_PREFIX:-} STUDIO_JOB_WORKER: thread STUDIO_API_TOKEN: "${STUDIO_API_TOKEN:?Set STUDIO_API_TOKEN to a secret you choose}" STUDIO_STILL_ADAPTER: ${STUDIO_STILL_ADAPTER:-stub} STUDIO_CLIP_ADAPTER: ${STUDIO_CLIP_ADAPTER:-stub} STUDIO_COMFY_STILL_LANES: ${STUDIO_COMFY_STILL_LANES:-} STUDIO_COMFY_CLIP_LANES: ${STUDIO_COMFY_CLIP_LANES:-} STUDIO_COMFY_IMAGE_LANES_FOR_FREE: ${STUDIO_COMFY_IMAGE_LANES_FOR_FREE:-} STUDIO_COMFY_ALLOW_HOSTS: ${STUDIO_COMFY_ALLOW_HOSTS:-} STUDIO_COMFY_H3_STYLES: ${STUDIO_COMFY_H3_STYLES:-} volumes: - studio-data:/data depends_on: studio-api: condition: service_started redis: profiles: ["celery"] image: redis:7-alpine ports: - "6379:6379" studio-celery: profiles: ["celery"] build: context: ./studio command: ["celery", "-A", "app.jobs.celery_app", "worker", "--loglevel=info"] environment: STUDIO_DATABASE_URL: ${STUDIO_DATABASE_URL:-sqlite:////data/studio.db} STUDIO_MEDIA_ROOT: /data/media STUDIO_MEDIA_BACKEND: ${STUDIO_MEDIA_BACKEND:-local} STUDIO_S3_BUCKET: ${STUDIO_S3_BUCKET:-} STUDIO_S3_ENDPOINT: ${STUDIO_S3_ENDPOINT:-} STUDIO_S3_PREFIX: ${STUDIO_S3_PREFIX:-} STUDIO_JOB_WORKER: celery STUDIO_API_TOKEN: "${STUDIO_API_TOKEN:?Set STUDIO_API_TOKEN to a secret you choose}" STUDIO_CELERY_BROKER_URL: ${STUDIO_CELERY_BROKER_URL:-redis://redis:6379/0} STUDIO_STILL_ADAPTER: ${STUDIO_STILL_ADAPTER:-stub} STUDIO_CLIP_ADAPTER: ${STUDIO_CLIP_ADAPTER:-stub} STUDIO_COMFY_STILL_LANES: ${STUDIO_COMFY_STILL_LANES:-} STUDIO_COMFY_CLIP_LANES: ${STUDIO_COMFY_CLIP_LANES:-} STUDIO_COMFY_IMAGE_LANES_FOR_FREE: ${STUDIO_COMFY_IMAGE_LANES_FOR_FREE:-} STUDIO_COMFY_ALLOW_HOSTS: ${STUDIO_COMFY_ALLOW_HOSTS:-} STUDIO_COMFY_H3_STYLES: ${STUDIO_COMFY_H3_STYLES:-} volumes: - studio-data:/data depends_on: redis: condition: service_started studio-api: condition: service_started postgres: profiles: ["postgres"] image: postgres:16-alpine environment: POSTGRES_DB: studio POSTGRES_USER: studio POSTGRES_PASSWORD: ${STUDIO_POSTGRES_PASSWORD:-studio} ports: - "5432:5432" volumes: - studio-pg:/var/lib/postgresql/data minio: profiles: ["minio"] image: minio/minio:latest command: ["server", "/data", "--console-address", ":9001"] environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER:-} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-} ports: - "9000:9000" - "9001:9001" volumes: - minio-data:/data volumes: studio-data: studio-pg: minio-data: