# FabledCurator in three containers — the install path. # # docker compose -f docker-compose.single.yml up -d # # Milestone 422 step 5. FabledCurator runs web and every worker lane inside # ONE container, with Postgres and Redis beside it. How much work each lane # does is then a dial in the web UI (Settings -> Activity -> Worker lanes), # live, with no compose edit and no restart. # # THE MULTI-SERVICE STACK IS NOT REPLACED. `docker-compose.yml` still runs the # five app services separately and is the right shape for a Swarm deployment # spread across hosts, where per-service rolling rollback and placement # constraints matter. This file is the adopter path: one box, one command. # # What consolidating costs, stated here rather than discovered later: # - Everything shares one host, so there is no spreading work across nodes. # - Rollback is all-or-nothing; there is no rolling back `web` alone. # - One stop timeout for the whole container, sized to the slowest lane. # # NOT a cost, recorded so it is not rediscovered and raised again: the # multi-service stack mounts /images:ro on ml-worker and one container cannot # mount one path two ways. Operator ruled that a non-issue (2026-09-22) — it # is the same codebase either way. # # FabledCurator has no authentication. Whatever can reach ${PORT} is an # administrator, including over the stored platform session cookies. Do not # publish this port beyond a network you trust — see "Before you expose it" # in README.md. services: redis: image: redis:7-alpine volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped postgres: image: pgvector/pgvector:pg16 environment: POSTGRES_USER: ${DB_USER:-curator} POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres} POSTGRES_DB: ${DB_NAME:-curator} volumes: - postgres_data:/var/lib/postgresql/data # pgvector index builds and the gallery's TABLESAMPLE reads both want more # shared memory than docker's 64MB default. shm_size: 512m healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-curator} -d ${DB_NAME:-curator}"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped fabledcurator: image: git.fabledsword.com/bvandeusen/fabledcurator:latest # Everything: hypercorn plus one celery process per lane, under # supervisord, whose config is generated from the application's own lane # table so the two cannot disagree. command: ["all"] # tini as PID 1, in front of supervisord. supervisord reaps its own # children, but a container's PID 1 also inherits orphans from anywhere # below — celery's prefork pool and gallery-dl's subprocesses both make # them. Without this they accumulate as zombies for the life of the # container. init: true # Sized to the SLOWEST lane, not the average. maintenance_long runs DB # backups, library audits and translation backfill, and gets 180s to # finish a chunk; the lanes stop in parallel, so this covers the max # rather than their sum. Below this, a routine restart becomes a SIGKILL # mid-backup — which is recoverable (the work is chunked and idempotent) # but wastes however long it had run. stop_grace_period: 200s # BOTH halves: hypercorn answers AND every configured lane is answering # the broker. A web-only check would report a healthy container while # every lane inside it had crashed — the failure mode consolidation # creates, since docker can no longer see the lanes as separate services. healthcheck: test: ["CMD", "python", "-m", "backend.app.scripts.healthcheck_all"] interval: 30s timeout: 15s retries: 3 # Covers alembic + hypercorn boot + four celery workers registering. start_period: 90s environment: DB_USER: ${DB_USER:-curator} DB_PASSWORD: ${DB_PASSWORD:-postgres} DB_HOST: postgres DB_PORT: "5432" DB_NAME: ${DB_NAME:-curator} CELERY_BROKER_URL: redis://redis:6379/0 CELERY_RESULT_BACKEND: redis://redis:6379/0 SECRET_KEY: ${SECRET_KEY:-change-me-before-you-expose-this} EXTENSION_API_KEY: ${EXTENSION_API_KEY:-} LOG_LEVEL: ${LOG_LEVEL:-INFO} ports: - "${PORT:-8080}:8080" volumes: - ${IMAGES_DIR:-./images}:/images # Read-only. The filesystem scan copies out of here and never writes to # it, so a mistake cannot reach the source library. - ${IMPORT_DIR:-./import}:/import:ro depends_on: postgres: { condition: service_healthy } redis: { condition: service_healthy } restart: unless-stopped volumes: redis_data: postgres_data: