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 spherelet size (for example Flex 512 MB, Standard 1 or Standard 2).
  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​