Service topology
Services
Frontend
- Image:
ghcr.io/qventu/exulu-frontend:latest - Port:
3000 - Role: The Next.js web application. Served directly to end-user browsers. Calls the backend over the network; the backend URL is passed via
NEXT_BACKENDat container startup.
Backend
- Port:
9001(HTTP API),4000(LiteLLM proxy — see below) - Role: The core application server. Handles all API requests from the frontend, manages agents, knowledge, users, budgets, and jobs. There is no published Docker image — the backend is always built locally from the
@exulu/backendnpm package (public registry, no token required; see Requirements).
LiteLLM proxy
- Port:
4000 - Role: An OpenAI-compatible proxy that the backend and workers use to reach LLM providers (Anthropic, Google Vertex AI, OpenAI, and others). The proxy is spawned by the backend process when
EXULU_USE_LITELLM=true; you do not run it as a separate service. Port4000is published so that worker containers can reach the proxy directly. - Config: Path set by
LITELLM_CONFIG_PATH(defaults to./config.litellm.yamlin the example repo). - Master key: Set via
LITELLM_MASTER_KEY. The backend and workers authenticate against the proxy with this key.
Worker
- Port:
9002 - Role: Processes background jobs (knowledge ingestion, embeddings, evals, routines). Connects to the LiteLLM proxy in client mode — it does not spawn its own proxy. Workers need 8 GB of Node.js heap (
NODE_OPTIONS='--max-old-space-size=8192') because knowledge processing tasks can hold large document representations in memory.
Postgres + pgvector
- Image:
pgvector/pgvector:pg17 - Port:
5432 - Role: Primary application database. Stores agents, users, sessions, knowledge items, chunks, and vector embeddings. The
pgvectorextension is required; plain Postgres without it will not work.
LiteLLM Postgres database
- Role: A separate Postgres database used exclusively by the LiteLLM proxy for user/key management, budget tracking, and spend logging.
- Connection string:
LITELLM_DATABASE_URL
Redis
- Image:
redis:7 - Port:
6379 - Role: Job queue backing for BullMQ. Required when running workers. Not required for backend-only deployments without background processing.
S3-compatible storage
- Role: File storage for uploads (chat attachments, session files, knowledge sources). The bucket must exist before the application starts — IMP does not create it.
- Example image (local):
quay.io/minio/minio:RELEASE.2025-04-22T22-12-26Z - Config prefix:
COMPANION_S3_*
Whisper GPU server (optional)
- Port:
9876 - Role: Transcription backend. Runs WhisperX on a GPU and exposes an HTTP endpoint the backend can call for audio/video transcription. Only required if you use the meeting transcription feature. Start it with
npx @exulu/backend exulu-start-whisper.
Public webhooks
Two backend HTTP endpoints must be reachable from the public internet — not only from within your Docker network.
Exposure guidance. Both paths must be reachable on the public URL you set in
BACKEND (used for routine webhook URLs and for Recall webhook subscription).
- Route traffic through your reverse proxy (Traefik, nginx, Caddy) to the backend’s internal port
9001. - If the backend sits behind a trusted proxy that appends the real client IP to
X-Forwarded-For, setEXULU_TRUST_PROXY=true. Without this flag, the raw socket address is used for rate-limit keying — which will be the proxy’s IP, not the originating client’s. - Do not expose the backend port directly to the internet; keep all other routes internal.
Network topology in production
Production deployments use Dokploy and connect services via an external Docker network nameddokploy-network. The docker-compose.backend.yml and docker-compose.worker.yml files in the example repo reference this network. In a non-Dokploy setup, replace dokploy-network with a network shared across your compose stacks.
The services compose file (docker-compose.services.yml) uses exulu-network as a bridge network — suitable for local development or when all services run in a single compose project.