name: koharu-suite x-kodama-image: &kodama-image image: ${KOHARU_IMAGE:-koharu-suite:local} x-postgres-environment: &postgres-environment POSTGRES_DB: ${POSTGRES_DB:-koharu} POSTGRES_HOST: db POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-koharu} POSTGRES_PORT: 5432 POSTGRES_USER: ${POSTGRES_USER:-koharu} services: db: image: postgres:18-alpine environment: POSTGRES_DB: ${POSTGRES_DB:-koharu} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-koharu} POSTGRES_USER: ${POSTGRES_USER:-koharu} healthcheck: test: - CMD-SHELL - pg_isready -U "$${POSTGRES_USER}" -d "$${POSTGRES_DB}" interval: 5s timeout: 5s retries: 10 ports: - "127.0.0.1:${POSTGRES_PUBLISHED_PORT:-5432}:5432" volumes: - postgres-data:/var/lib/postgresql migrate: <<: *kodama-image build: context: . args: REVISION: ${KOHARU_REVISION:-unknown} SOURCE: ${KOHARU_SOURCE:-https://github.com/cosZone/koharu-suite} VERSION: ${KOHARU_VERSION:-dev} command: - node - dist/cli.js - migrate depends_on: db: condition: service_healthy environment: <<: *postgres-environment init: true restart: "no" doctor: <<: *kodama-image command: - node - dist/cli.js - doctor depends_on: db: condition: service_healthy environment: <<: *postgres-environment BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?Set BETTER_AUTH_SECRET to at least 32 high-entropy characters} BETTER_AUTH_URL: ${BETTER_AUTH_URL:?Set BETTER_AUTH_URL to the canonical public origin} PUBLIC_CORS_ORIGINS: ${PUBLIC_CORS_ORIGINS:-} PUBLIC_RATE_LIMIT_MAX: ${PUBLIC_RATE_LIMIT_MAX:-120} PUBLIC_RATE_LIMIT_WINDOW_SECONDS: ${PUBLIC_RATE_LIMIT_WINDOW_SECONDS:-60} TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:?Set TELEGRAM_BOT_TOKEN} TELEGRAM_CHANNEL_ID: ${TELEGRAM_CHANNEL_ID:-} TELEGRAM_WORKER_CONCURRENCY: ${TELEGRAM_WORKER_CONCURRENCY:-4} TRUST_PROXY: ${TRUST_PROXY:-false} init: true profiles: - ops restart: "no" server: <<: *kodama-image command: - node - dist/cli.js - serve depends_on: migrate: condition: service_completed_successfully environment: <<: *postgres-environment ADMIN_ASSETS_ROOT: /app/admin BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?Set BETTER_AUTH_SECRET to at least 32 high-entropy characters} BETTER_AUTH_URL: ${BETTER_AUTH_URL:?Set BETTER_AUTH_URL to the canonical public origin} MEDIA_CACHE_ENABLED: ${MEDIA_CACHE_ENABLED:-false} MEDIA_CACHE_MAX_BYTES: ${MEDIA_CACHE_MAX_BYTES:-5368709120} MEDIA_CACHE_ROOT: /var/lib/koharu/media-cache PORT: 3000 PUBLIC_CORS_ORIGINS: ${PUBLIC_CORS_ORIGINS:-} PUBLIC_RATE_LIMIT_MAX: ${PUBLIC_RATE_LIMIT_MAX:-120} PUBLIC_RATE_LIMIT_WINDOW_SECONDS: ${PUBLIC_RATE_LIMIT_WINDOW_SECONDS:-60} S3_ALLOW_INSECURE: ${S3_ALLOW_INSECURE:-false} S3_BUCKET: ${S3_BUCKET:-} S3_CONNECT_TIMEOUT_MS: ${S3_CONNECT_TIMEOUT_MS:-5000} S3_ENDPOINT: ${S3_ENDPOINT:-} S3_FORCE_PATH_STYLE: ${S3_FORCE_PATH_STYLE:-true} S3_KEY: ${S3_KEY:-} S3_MAX_BYTES: ${S3_MAX_BYTES:-5368709120} S3_PREFIX: ${S3_PREFIX:-koharu/media-cache} S3_REGION: ${S3_REGION:-us-east-1} S3_REQUEST_TIMEOUT_MS: ${S3_REQUEST_TIMEOUT_MS:-30000} S3_SECRET: ${S3_SECRET:-} TRUST_PROXY: ${TRUST_PROXY:-false} healthcheck: test: - CMD - node - -e - fetch('http://localhost:3000/readyz').then((response) => { if (!response.ok) process.exit(1) }).catch(() => process.exit(1)) interval: 10s timeout: 5s retries: 5 start_period: 10s init: true ports: - "${KOHARU_HTTP_PORT:-3000}:3000" restart: unless-stopped stop_grace_period: 30s volumes: - media-cache-data:/var/lib/koharu/media-cache:ro worker: <<: *kodama-image command: - node - dist/cli.js - worker depends_on: migrate: condition: service_completed_successfully environment: <<: *postgres-environment MEDIA_CACHE_DOWNLOAD_CONCURRENCY: ${MEDIA_CACHE_DOWNLOAD_CONCURRENCY:-2} MEDIA_CACHE_ENABLED: ${MEDIA_CACHE_ENABLED:-false} MEDIA_CACHE_MAX_BYTES: ${MEDIA_CACHE_MAX_BYTES:-5368709120} MEDIA_CACHE_ROOT: /var/lib/koharu/media-cache S3_ALLOW_INSECURE: ${S3_ALLOW_INSECURE:-false} S3_BUCKET: ${S3_BUCKET:-} S3_CONNECT_TIMEOUT_MS: ${S3_CONNECT_TIMEOUT_MS:-5000} S3_ENDPOINT: ${S3_ENDPOINT:-} S3_FORCE_PATH_STYLE: ${S3_FORCE_PATH_STYLE:-true} S3_KEY: ${S3_KEY:-} S3_MAX_BYTES: ${S3_MAX_BYTES:-5368709120} S3_PREFIX: ${S3_PREFIX:-koharu/media-cache} S3_REGION: ${S3_REGION:-us-east-1} S3_REQUEST_TIMEOUT_MS: ${S3_REQUEST_TIMEOUT_MS:-30000} S3_SECRET: ${S3_SECRET:-} TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:?Set TELEGRAM_BOT_TOKEN} TELEGRAM_CHANNEL_ID: ${TELEGRAM_CHANNEL_ID:-} TELEGRAM_WORKER_CONCURRENCY: ${TELEGRAM_WORKER_CONCURRENCY:-4} healthcheck: test: - CMD - node - dist/cli.js - health - worker interval: 10s timeout: 5s retries: 5 start_period: 15s init: true restart: unless-stopped stop_grace_period: 30s volumes: - media-cache-data:/var/lib/koharu/media-cache volumes: media-cache-data: postgres-data: