Server and worker split
IMP separates two process roles:
Workers are optional. You can run IMP without any worker containers, but background features (knowledge processing, eval runs, routine scheduling) will not function. The administration UI shows a warning banner when Redis / workers are not configured.
A worker connects to the same Postgres and Redis instances as the server. It does not spawn its own LiteLLM process; it connects to the server’s LiteLLM proxy on port
4000.
Scaling workers
To handle more concurrent background jobs, run more worker containers. Each worker container subscribes to the same BullMQ queue in Redis; BullMQ distributes jobs across all subscribed workers automatically. Each worker container subscribes to all configured queues by default. Queue scoping (subscribing a worker to only a subset of queues) is controlled by theExuluQueueConfig passed to createWorkers — this is a code-level concern for custom deployments built on @exulu/backend.
Memory. Give each worker container at least 8 GB. Knowledge processing holds large document representations in memory during chunking and embedding. Set the Node.js heap limit explicitly:
start-worker.sh does not set NODE_OPTIONS itself — the value above must come from the compose environment block or another injection mechanism.
Queue concurrency and timeouts
Each queue is configured with:
These values are set in the
ExuluQueueConfig objects you define when configuring @exulu/backend.
Sizing embedder queues to provider rate limits
Embedding providers enforce token-per-minute or request-per-minute limits. Setratelimit and concurrency.worker to stay within those limits.
Example: a provider allows 5 million tokens per minute and your average chunk is 500 tokens.
ratelimit: 166 on the embedder queue. With a single worker at concurrency.worker: 4, each of the 4 concurrent slots can embed ~41 chunks per second before hitting the limit. Adjust based on observed latency and actual provider quota.
pm2 in containers
Both the backend and worker run under pm2 inside the container:docker logs <container> or the pm2 log stream to detect a crashed process.
Useful commands when exec’d into a running container:
Upgrade procedure
1
Bump
@exulu/backend in package.json2
In your fork or checkout of
exulu-example, update the version of @exulu/backend to the target release.3
Rebuild the images
4
docker compose -f docker-compose.backend.yml build
docker compose -f docker-compose.worker.yml build
5
Restart containers
6
docker compose -f docker-compose.backend.yml up -d
docker compose -f docker-compose.worker.yml up -d
7
The start scripts run
initdb on every boot. Any new tables or columns are added automatically on startup — there is nothing to run manually.8
Verify
9
Check the container logs for the
[EXULU] Database initialized. log line. Check that pm2 shows the process as online.Back up both Postgres databases before upgrading in production.
initdb does not run destructive migrations, but it is good practice to have a clean snapshot before any software change.Dokploy network
The backend and worker compose files attach todokploy-network, an external Docker network managed by Dokploy. Dokploy creates this network automatically and injects environment variables into containers.
If you are not using Dokploy, create your own shared network and update the networks section in both compose files: