docker-compose.yml
Rama main · réplica de lectura del repositorio original.
name: paperlab
x-backend: &backend
build: ./backend
# Optional: every setting has a default in app/config.py (Docker Compose 2.24 or later).
env_file:
- path: .env
required: false
volumes:
- ./backend:/src
- pdfs:/data/pdfs
- models:/models
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
# What the main and the ingest worker share; only the main worker compiles, so only it gets TECTONIC_USER.
x-worker-env: &worker-env
DISCOVERY_PROVIDER: ${DISCOVERY_PROVIDER:-live}
# Search sources behind a model connection embed here, so the E2E stack's fake providers must reach the worker
# too: a real source would record real names while the API reads `fake:` ones.
LLM_PROVIDER: ${LLM_PROVIDER:-ollama}
# The built-in search model's folder. E2E's no-search-model run points it at an empty one (README).
MODELS_DIR: ${MODELS_DIR:-/models}
# Threads the search model may use; unset it takes min(4, cores). Uncomment to change that.
# EMBED_THREADS: 4
PAPERLAB_STACK: ${COMPOSE_PROJECT_NAME}
DEV_RELOAD: ${DEV_RELOAD:-} # see the api
services:
db:
image: pgvector/pgvector:pg16
environment:
POSTGRES_USER: paperlab
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-paperlab}
POSTGRES_DB: paperlab
ports: ["127.0.0.1:${DB_PORT:-5433}:5432"]
volumes: [pgdata:/var/lib/postgresql/data]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U paperlab"]
interval: 2s
retries: 30
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 2s
retries: 30
api:
<<: *backend
# Shell > .env > default. E2E runs on its own stack: scripts/e2e-stack.sh (README).
environment:
LLM_PROVIDER: ${LLM_PROVIDER:-ollama}
DISCOVERY_PROVIDER: ${DISCOVERY_PROVIDER:-live}
# The compose project (`paperlab`, or `paperlab-e2e` from scripts/e2e-stack.sh). Fake providers refuse to start
# unless it ends in -e2e (D273).
PAPERLAB_STACK: ${COMPOSE_PROJECT_NAME}
# The folder `docker compose up` ran in, for the Connect Claude page (Compose uses its own working directory
# when the shell exports no PWD).
PAPERLAB_DIR: ${PWD:-}
# The built-in search model's folder. E2E's no-search-model run points it at an empty one (README).
MODELS_DIR: ${MODELS_DIR:-/models}
# Any non-empty DEV_RELOAD (e.g. DEV_RELOAD=1) restarts the api and workers on every saved change under backend/.
# Off by default: when your own library runs from this folder, a save would cancel its running jobs. scripts/e2e-stack.sh turns it on.
DEV_RELOAD: ${DEV_RELOAD:-}
# Killed for memory (a test stack beside it did that twice on 2026-10-15) or crashed, it comes back; stopped by hand,
# it stays stopped. on-failure, not always: nothing starts with Docker itself. The workers set it too.
restart: on-failure
command: sh -c 'alembic upgrade head && exec uvicorn app.main:app --host 0.0.0.0 --port 8000 $${DEV_RELOAD:+--reload}'
ports: ["127.0.0.1:${API_PORT:-8000}:8000"]
worker: &worker
<<: *backend
restart: on-failure # see the api
# The References fetch runs here, so the E2E stack's fake discovery must reach the worker too.
environment:
<<: *worker-env
TECTONIC_USER: tex # manuscripts compile as this unprivileged account (the ingest worker never compiles)
# With DEV_RELOAD, watchfiles, not `arq --watch`: that restarts the worker in-process and never re-imports changed
# code.
command: >-
sh -c 'if [ -n "$$DEV_RELOAD" ]; then exec watchfiles --filter python "arq app.workers.settings.WorkerSettings" app;
else exec arq app.workers.settings.WorkerSettings; fi'
ingest-worker:
<<: *worker
environment: *worker-env
# A paper's ingest and re-embed, one paper at a time: ten at once ran the worker out of memory.
command: >-
sh -c 'if [ -n "$$DEV_RELOAD" ]; then exec watchfiles --filter python "arq app.workers.settings.IngestWorkerSettings" app;
else exec arq app.workers.settings.IngestWorkerSettings; fi'
frontend:
build: ./frontend
environment:
API_URL: http://api:8000
ports: ["127.0.0.1:${FRONTEND_PORT:-5180}:5173"]
volumes:
- ./frontend:/app
- /app/node_modules
depends_on: [api]
volumes:
pgdata:
pdfs:
models: