Skip to main content

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.
docker-compose.worker.yml does not include an env_file entry. Outside Dokploy, the worker starts without database and Redis credentials and crashes immediately. Add the following to docker-compose.worker.yml under the worker service:
Or inject the required environment variables through another mechanism before starting the worker.

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 the ExuluQueueConfig 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. Set ratelimit and concurrency.worker to stay within those limits. Example: a provider allows 5 million tokens per minute and your average chunk is 500 tokens.
Set 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:
pm2 restarts the Node.js process automatically on crash. If the process crashes repeatedly and pm2 gives up, the container itself does not exit — you need to check 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.json
2
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 to dokploy-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:
Make sure all containers that need to communicate (backend, worker, Postgres, Redis, MinIO) are attached to the same network.