Skip to main content

Background worker

A background worker is a containerized process that runs continuously on ComputeSphere without exposing a public URL. It wakes up on its own — usually because there's something to consume from a queue, a stream, a database, or a remote API — does the work, and keeps looping.

The three service kinds — by what makes them run
Web serviceweb-service
RequestRuns in response to incoming HTTP requests.
Background workerbackground-worker
ContinuousRuns continuously. Polls a queue, listens to a stream, loops a job.
Cron jobcron-job
ScheduleRuns on a cron tick, exits, waits for the next tick.

Background worker is the right kind for: queue consumers, polling jobs, stream processors, ETL pipelines, AI inference workers, IoT message handlers, file processors, log shippers — anything that doesn't wait for an HTTP request to do its job.

What you configure​

FieldWhat it does
NameIdentifier for the worker — unique within the project.
PlanSpherelet shape: flex, standard, or performance. See Spherelets.
SphereletsHow many copies to run in parallel (1–100). For queue consumers, more spherelets = more throughput.
Build sourceimage to pull a pre-built image, or git to build from a connected repo.
ImageRegistry path (when build_source: image) — e.g. ghcr.io/acme/worker:1.0.0.
DockerfilePath to the Dockerfile (when build_source: git) — defaults to Dockerfile.
BranchGit branch to deploy from (when build_source: git) — defaults to main.
CommandOptional. Overrides the container's default CMD — e.g. node workers/process-queue.js. Useful when the same image runs different worker entrypoints in different services.
AutoscalingOptional. CPU-based. Set min/max spherelets and a target. See Auto-scaling.
Environment variablesConfiguration passed into the container — queue URLs, API keys, feature flags. See Environment variables.

Conspicuously absent compared to web service: no port, no health check path, no custom domain. A background worker doesn't accept incoming traffic, so the platform has nothing to route to it and no HTTP probe to fire.

How it differs from the other service kinds​

AspectWeb serviceBackground workerCron job
TriggerHTTP requestContinuous loopCron schedule
Public URLYesNoNo
LifetimeLong-runningLong-runningOne run per tick
Port requiredYesNoNo
Health checkHTTP pathProcess livenessExit code
Scaling shapeMore spherelets = more concurrencyMore spherelets = more parallel workersOne container per tick
Best forAPIs, web appsQueue consumers, polling, ETLScheduled batch jobs

If your work is request-driven, use Web service. If it's on a schedule, use Cron job. Background worker fills the gap in between.

Common patterns​

Queue consumer​

The worker pulls messages from a queue (Valkey list, RabbitMQ, SQS, etc.) and processes them. The container's main loop blocks waiting for the next message:

// node workers/process-orders.js
import Valkey from "iovalkey";

const valkey = new Valkey(process.env.VALKEY_URL);

while (true) {
// BLPOP blocks until a message arrives, with a timeout.
const message = await valkey.blpop("orders:queue", 30);
if (!message) continue;

await processOrder(JSON.parse(message[1]));
}

Scale spherelets up for more concurrent processing. Each spherelet runs its own copy of the loop and competes for messages — most queue brokers handle the distribution automatically.

Polling worker​

The worker polls a remote source (API, database, S3 bucket) on an interval. Different from cron jobs in that the worker chooses its own cadence and holds state between polls:

# python workers/sync_repos.py
import time
import requests

CURSOR = None
while True:
response = requests.get(f"https://api.github.com/.../events?since={CURSOR}")
events = response.json()
if events:
process_events(events)
CURSOR = events[-1]["timestamp"]
time.sleep(15)

Scale to one spherelet — multiple pollers would compete and duplicate work.

Stream processor​

The worker subscribes to a stream (Kafka, Valkey Streams, Server-Sent Events) and processes events as they arrive. Similar to queue consumer, but the stream's ordering and replay semantics shape how you scale.

Lifecycle controls​

A background worker supports the same lifecycle verbs as a web service — deploy, redeploy, restart, stop, start, rollback, scale. See Service actions.

The big behavioral difference: a restart drops in-flight work. If your worker is mid-message when its spherelet restarts, the message goes back to the queue (assuming you're using a broker with at-least-once delivery). Design your loop to be idempotent — every message processed at least once, no duplicates of side effects.

Create a background worker​

  1. Open the project and environment where the worker should run.
  2. Click New service → Background worker.
  3. Pick a build source: Image or Git.
  4. Fill in name and a compute plan (Flex / Standard / Performance).
  5. (Optional) Override the container's default command if the image runs multiple worker entrypoints.
  6. (Optional) Add environment variables for queue URLs, API keys, feature flags.
  7. (Optional) Turn on autoscaling with a min/max range and CPU target.
  8. Click Create.

The worker appears in the Services tab alongside web services and cron jobs.

Honest limits​

  • No graceful drain. When a worker is stopped, restarted, or redeployed, the platform sends SIGTERM and gives the process a short window to exit before SIGKILL. Long-running message processing should checkpoint progress and tolerate being interrupted.
  • No retries on the platform side. If a worker crashes, the spherelet restarts — but the message it was processing is broker-side. Use a broker with delivery guarantees (RabbitMQ ack, SQS visibility timeout, Kafka offset commits) so you don't lose work.
  • CPU-only autoscaling. Memory pressure, queue depth, and message age aren't autoscaling signals today. Scale on those manually, or trigger scale from a separate metric pipeline.
  • One command per worker. If you want the same image to run a queue consumer AND a polling worker, create two background workers in the same project — they share the image, each has its own command.

What's next​