name: vignette services: db: image: pgvector/pgvector:pg16 restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER:-vignette_owner} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set in .env} POSTGRES_DB: ${POSTGRES_DB:-vignette} APP_DB_USER: ${APP_DB_USER:-vignette_app} APP_DB_PASSWORD: ${APP_DB_PASSWORD:?set in .env} volumes: - pgdata:/var/lib/postgresql/data - ./db/init:/docker-entrypoint-initdb.d:ro healthcheck: test: - CMD-SHELL - >- PGPASSWORD=$${APP_DB_PASSWORD} psql -U $${APP_DB_USER} -d $${POSTGRES_DB} -tAc "SELECT to_regclass('app.ci_regression_dag_node') IS NOT NULL" | grep -qx t interval: 10s timeout: 5s retries: 5 start_period: 5m networks: [vignette] api: build: context: .. dockerfile: apps/api/Dockerfile args: INSTALL_RAG: ${INSTALL_RAG:-false} restart: unless-stopped depends_on: db: { condition: service_healthy } environment: ENVIRONMENT: ${ENVIRONMENT:?set ENVIRONMENT in .env} DATABASE_URL: postgresql://${APP_DB_USER:-vignette_app}:${APP_DB_PASSWORD}@db:5432/${POSTGRES_DB:-vignette} ENGINE_URL: ${ENGINE_URL:-http://host.docker.internal:9099} ENGINE_MODE: ${ENGINE_MODE:-claude_cli} ENGINE_GATEWAY_SHARED_SECRET: ${ENGINE_GATEWAY_SHARED_SECRET:-} ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} OPENAI_API_KEY: ${OPENAI_API_KEY:?set in .env} OPENAI_BASE_URL: ${OPENAI_BASE_URL:-https://api.openai.com/v1} VIGNETTE_VOICE_STT_PROVIDER: ${VIGNETTE_VOICE_STT_PROVIDER:-openai} DEEPGRAM_API_KEY: ${DEEPGRAM_API_KEY:-} DEEPGRAM_STT_URL: ${DEEPGRAM_STT_URL:-wss://api.deepgram.com/v1/listen} DEEPGRAM_STT_MODEL: ${DEEPGRAM_STT_MODEL:-nova-3} DEEPGRAM_STT_LANGUAGE: ${DEEPGRAM_STT_LANGUAGE:-ko} DEEPGRAM_ENDPOINTING_MS: ${DEEPGRAM_ENDPOINTING_MS:-300} DEEPGRAM_UTTERANCE_END_MS: ${DEEPGRAM_UTTERANCE_END_MS:-1200} DEEPGRAM_KEEPALIVE_SECONDS: ${DEEPGRAM_KEEPALIVE_SECONDS:-4} DEEPGRAM_FINALIZE_TIMEOUT_SECONDS: ${DEEPGRAM_FINALIZE_TIMEOUT_SECONDS:-15} DEEPGRAM_MIP_OPT_OUT: ${DEEPGRAM_MIP_OPT_OUT:-true} SESSION_SECRET: ${SESSION_SECRET:?set in .env} VIGNETTE_RUPTURE_INTERNAL_TOKEN: ${VIGNETTE_RUPTURE_INTERNAL_TOKEN:?set in .env} VIGNETTE_PRACTICE_INTERNAL_TOKEN: ${VIGNETTE_PRACTICE_INTERNAL_TOKEN:?set in .env} VIGNETTE_CALIBRATION_TRANSFER_INTERNAL_TOKEN: ${VIGNETTE_CALIBRATION_TRANSFER_INTERNAL_TOKEN:?set in .env} VIGNETTE_SUPERVISION_RESEARCH_INTERNAL_TOKEN: ${VIGNETTE_SUPERVISION_RESEARCH_INTERNAL_TOKEN:?set in .env} VIGNETTE_MULTIMODAL_ALLIANCE_INTERNAL_TOKEN: ${VIGNETTE_MULTIMODAL_ALLIANCE_INTERNAL_TOKEN:?set in .env} VIGNETTE_CONTINUOUS_IMPROVEMENT_INTERNAL_TOKEN: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_INTERNAL_TOKEN:?set in .env} VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_ENABLED: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_ENABLED:-false} VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_INTERVAL_SECONDS: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_INTERVAL_SECONDS:-3600} VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_STARTUP_DELAY_SECONDS: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_STARTUP_DELAY_SECONDS:-30} VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_RETRY_DELAY_SECONDS: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_RETRY_DELAY_SECONDS:-300} VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_LEASE_TIMEOUT_SECONDS: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_LEASE_TIMEOUT_SECONDS:-1800} VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_ENGINE_TIMEOUT_SECONDS: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_ENGINE_TIMEOUT_SECONDS:-300} VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_BATCH_SIZE: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_BATCH_SIZE:-1} VIGNETTE_CONTINUOUS_IMPROVEMENT_DRIFT_TRIGGER_ENABLED: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_DRIFT_TRIGGER_ENABLED:-false} VIGNETTE_CONTINUOUS_IMPROVEMENT_ROLLBACK_EXECUTOR_ENABLED: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_ROLLBACK_EXECUTOR_ENABLED:-false} VIGNETTE_CONTINUOUS_IMPROVEMENT_ROLLBACK_EXECUTOR_ENDPOINT: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_ROLLBACK_EXECUTOR_ENDPOINT:-} VIGNETTE_CONTINUOUS_IMPROVEMENT_ROLLBACK_EXECUTOR_TOKEN: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_ROLLBACK_EXECUTOR_TOKEN:-} VIGNETTE_CONTINUOUS_IMPROVEMENT_ROLLBACK_EXECUTOR_TIMEOUT_SECONDS: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_ROLLBACK_EXECUTOR_TIMEOUT_SECONDS:-30} OAUTH_GOOGLE_CLIENT_ID: ${OAUTH_GOOGLE_CLIENT_ID:?set in .env} OAUTH_GOOGLE_CLIENT_SECRET: ${OAUTH_GOOGLE_CLIENT_SECRET:?set in .env} OAUTH_REDIRECT_URI: ${OAUTH_REDIRECT_URI:-https://api-vignette.chanpaca.net/auth/callback} AUTH_ALLOWED_EMAIL_DOMAINS: '${AUTH_ALLOWED_EMAIL_DOMAINS:-["hs.ac.kr","twentyoz.kr"]}' AUTH_TEACHER_EMAILS: '${AUTH_TEACHER_EMAILS:-[]}' AUTH_ADMIN_EMAILS: '${AUTH_ADMIN_EMAILS:-[]}' AUTH_DEV_LOGIN_ENABLED: ${AUTH_DEV_LOGIN_ENABLED:-false} AUTH_DEV_LOGIN_EXTRA_ORIGINS: '${AUTH_DEV_LOGIN_EXTRA_ORIGINS:-[]}' DEFAULT_AFFILIATION: ${DEFAULT_AFFILIATION:-} AUTO_SEED_PERSONAS: ${AUTO_SEED_PERSONAS:-false} ALLOW_SEED_PERSONA_FALLBACK: ${ALLOW_SEED_PERSONA_FALLBACK:-false} EVALUATOR_GOLDEN_FEWSHOT_ENABLED: ${EVALUATOR_GOLDEN_FEWSHOT_ENABLED:-false} FRONTEND_BASE_URL: ${FRONTEND_BASE_URL:-https://vignette.chanpaca.net} FRONTEND_ORIGIN_MAP: '${FRONTEND_ORIGIN_MAP:?set FRONTEND_ORIGIN_MAP in .env}' CORS_ORIGINS: '${CORS_ORIGINS:-["https://vignette.chanpaca.net","https://vnet.18ka.net","https://vignette-b1q.pages.dev"]}' USER_UPLOAD_DIR: ${USER_UPLOAD_DIR:-/app/uploads} NOTIFICATION_EMAIL_PROVIDER: ${NOTIFICATION_EMAIL_PROVIDER:-disabled} NOTIFICATION_EMAIL_MAX_ATTEMPTS: ${NOTIFICATION_EMAIL_MAX_ATTEMPTS:-3} NOTIFICATION_EMAIL_RETRY_SECONDS: ${NOTIFICATION_EMAIL_RETRY_SECONDS:-900} SMTP_HOST: ${SMTP_HOST:-} SMTP_PORT: ${SMTP_PORT:-587} SMTP_USERNAME: ${SMTP_USERNAME:-} SMTP_PASSWORD: ${SMTP_PASSWORD:-} SMTP_FROM_EMAIL: ${SMTP_FROM_EMAIL:-} SMTP_FROM_NAME: ${SMTP_FROM_NAME:-Vignette} SMTP_STARTTLS: ${SMTP_STARTTLS:-true} SMTP_SSL: ${SMTP_SSL:-false} extra_hosts: - "host.docker.internal:host-gateway" volumes: - apiuploads:/app/uploads healthcheck: test: - CMD - python - -c - >- import json, urllib.request; payload=json.load(urllib.request.urlopen('http://localhost:8000/health')); assert payload.get('status') == 'ok' and payload.get('db') is True and payload.get('engine') is True interval: 15s timeout: 5s retries: 5 networks: [vignette] web: build: context: ../apps/web dockerfile: Dockerfile args: VITE_API_BASE: ${PUBLIC_API_BASE:-/api} restart: unless-stopped depends_on: [api] networks: [vignette] proxy: image: caddy:2-alpine restart: unless-stopped depends_on: [web, api] ports: - "${HTTP_PORT:-8080}:80" - "${HTTPS_PORT:-8443}:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddydata:/data networks: [vignette] volumes: pgdata: apiuploads: caddydata: networks: vignette: driver: bridge