Worker Split
By default a single Breeze API container does everything: it serves the dashboard and API, holds the WebSocket connections your agents connect to, and runs every background job on a schedule. That is the right shape for most deployments and nothing about it has changed.
On a busy instance, though, the heavy scheduled work — metric rollups, retention sweeps, monitoring and SNMP polling, backup and discovery jobs — can compete with the requests your technicians are waiting on. The worker split is an optional second container that takes those jobs off the API, so a long rollup can no longer make the dashboard feel slow.
What moves and what stays
Section titled “What moves and what stays”| Runs in the worker container | Stays on the API container |
|---|---|
| Scheduled and recurring jobs — metric rollups, retention and cleanup sweeps, report generation, sync jobs | Everything that talks directly to a connected agent over its live WebSocket |
| Monitoring, SNMP, backup and discovery jobs | The HTTP API and dashboard |
| Durable event dispatch consumers | Agent enrollment and command dispatch |
Jobs that need to reach an agent’s live connection stay on the API container, because that is where the connection lives. Everything else — the majority of Breeze’s background work — moves.
Choosing the roles
Section titled “Choosing the roles”Two settings decide who runs what:
| Setting | Values | Meaning |
|---|---|---|
BREEZE_API_ROLE |
all (default), api |
all keeps today’s single-container behaviour. api tells the API container to stop running background jobs because a worker container is running them. |
BREEZE_ROLE |
all, api, worker |
The role the process actually runs as. You normally leave this alone — the compose file derives it from BREEZE_API_ROLE, and the worker container sets it to worker for you. |
Before you start
Section titled “Before you start”- Your image must be new enough to contain the worker. Confirm with
docker exec breeze-api ls dist/worker.cjs. If that file is missing, upgrade before going further — an older image has no worker to start and the container will restart in a loop. APP_ENCRYPTION_KEY_IDmust be set in your.env. Once the two containers are split, commands relayed between them are individually encrypted, and Breeze refuses to start either process without this key rather than falling back to something weaker. Setting it while you are still unsplit changes nothing, so it is safe to do in advance.- Confirm your compose file actually has the
workerservice. If you maintain your owndocker-compose.ymlrather than using the shipped one, copy theworkerservice block across before you begin.
Enable the split
Section titled “Enable the split”Do this one server at a time, and never on two regions simultaneously.
-
Upgrade first, with the API still doing everything.
Roll out the new image as a normal upgrade and leave
BREEZE_API_ROLEalone. Confirm the instance is healthy before changing any roles. -
Start the worker container.
Terminal window docker compose --profile worker-split up -d workerYou can also add
worker-splittoCOMPOSE_PROFILESin.envand run your usualdocker compose up -d. At this point the API is still running every job as well — this step only proves the worker starts cleanly. -
Check it is actually running and ready.
Terminal window docker ps --format '{{.Names}}' | grep breeze-workerdocker exec breeze-worker wget -qO- http://127.0.0.1:3001/health/readyWait until this reports ready before going on. If it reports that migrations are pending, or that it cannot reach the database or Redis, stop and resolve that first.
-
Hand the background jobs over.
Set
BREEZE_API_ROLE=apiin.env, then:Terminal window docker compose up -d apiWait for the API to report healthy. From here, scheduled jobs run only in the worker container.
-
Verify the split took effect.
- Send a command to a connected device — it should still work, because the API still owns the agent connection.
- Pick a scheduled job with a visible result and confirm it now appears in
docker logs breeze-workerand no longer indocker logs breeze-api.
-
Soak before repeating.
Watch both containers through at least one full cycle of your slowest scheduled job before splitting a second server.
Rolling back
Section titled “Rolling back”Give the jobs back to the API before stopping the worker. Doing it the other way around leaves nothing running them.
- Set
BREEZE_API_ROLE=allin.envand rundocker compose up -d api. - Wait for the API to report healthy — it is running every job again.
- Stop the worker:
docker compose stop worker.
Monitoring the worker
Section titled “Monitoring the worker”The worker publishes its own metrics at /metrics on port 3001 inside its
container. This is a separate scrape target from the API’s
/metrics/scrape — if you do not add it to your monitoring configuration, the
container running your heaviest jobs is invisible.
- Authentication is the same
METRICS_SCRAPE_TOKENbearer token the API uses, so the worker already has it. - If you use
METRICS_SCRAPE_IP_ALLOWLIST, note that the worker matches the address that actually connects to it and ignores forwarding headers. Put a proxy’s own address on the allowlist, not the scraper’s. - The worker reports runtime health — event-loop responsiveness and database pool usage — which is exactly what matters on the container doing the heavy lifting. It does not report HTTP or fleet metrics, because it serves no requests. Do not alert on their absence here.
Keeping the worker upgraded
Section titled “Keeping the worker upgraded”If your deploy process names services explicitly rather than running a bare
docker compose up -d, add worker to that list. Otherwise the API and web
containers move to each new version while the worker silently stays behind on
an old image. After every upgrade, confirm that every running Breeze container
reports the version you just deployed.