Background workers

Queue consumers and long-running jobs that scale on how much work is waiting.

A background worker runs continuously like a web service, but receives no HTTP traffic. It has no port and no URL. Workers exist for the work that should not happen inside a request: sending email, processing uploads, generating reports, consuming a message queue.

Plan requirement

Workers are available on Pro and Team. The Starter plan includes no workers.

How they differ from web services

Three differences matter in practice.

A worker has no port. Nothing routes to it, so there is nothing to expose and no health check based on responding to requests.

A worker scales on backlog, not on traffic. Where a web service is scaled for concurrent requests, a worker can be scaled by how much work is queued — adding consumers when the queue grows and removing them when it drains.

A worker's failure mode is silence. A broken web service returns errors someone notices. A broken worker simply stops consuming, and the queue grows until something else breaks. Monitor queue depth, not just whether the process is alive.

Create one

bash
guidal services create emailer \
  --image ghcr.io/your-org/your-app:v1 \
  --type worker \
  --command "python -m app.worker"

No --port is given. --command overrides the image's default entrypoint, which is how one image commonly serves as both the web service and the worker.

Scale

bash
guidal services scale emailer --replicas 4

Make sure the work itself can be processed in parallel before scaling — consumers competing for a queue that guarantees ordering will not go faster, and may process out of order.

Inspect

bash
guidal services list
guidal logs query --service emailer
guidal metrics service emailer

Since a worker produces no HTTP signal, logs and metrics are the only way to see what it is doing. Log the start and end of each job with an identifier.

Sharing an image with a web service

Most applications deploy the same image twice with different commands:

bash
guidal services create api --image ghcr.io/your-org/app:v1 --port 8080
guidal services create emailer --image ghcr.io/your-org/app:v1 \
  --type worker --command "python -m app.worker"

Deploy them together when a change spans both, and remember they are separate services — deploying one does not deploy the other:

bash
guidal services deploy api --image ghcr.io/your-org/app:v2
guidal services deploy emailer --image ghcr.io/your-org/app:v2

Writing a worker that survives restarts

A worker will be stopped and restarted — for deploys, scaling, and host maintenance. Two properties make that uneventful.

Handle termination signals: finish the job in flight, acknowledge it, then exit. A worker killed mid-job leaves that job unacknowledged, and it will be redelivered.

Make jobs idempotent. Because unacknowledged jobs are redelivered, any job may run more than once. Processing a payment twice is a much worse outcome than processing it zero times, so make the second run a no-op.

Plan limits

PlanMax workers
Starter0
Pro10
Team25