---
id: platform/background-workers
title: Background workers
description: Long-running processes with no HTTP port — queue consumers, stream processors — deployed and managed like any other Bex service.
keywords: [bex, background worker, queue, worker, no http port]
last_updated: 2026-09-23
---

Background workers are long-running processes with no HTTP port — queue consumers,
stream processors, or any workload that does useful work without serving traffic. They
share the same deploy, secrets, and lifecycle machinery as web services. Use a [cron
job](./cron-jobs.md) when the process should start on a schedule and exit after finishing.

## Declare a worker

In `render.yaml` — the worker is a sibling of the web service, often sharing the same
repo and image but branching on an env var:

```yaml
# render.yaml — from examples/stack-demo/render.yaml
services:
  - name: web
    type: web
    runtime: docker
    repo: https://github.com/bex-co/bex
    rootDir: examples/stack-demo
    plan: free
    healthCheckPath: /healthz
    envVars:
      - key: DATABASE_URL
        fromDatabase:
          name: db
          property: connectionString

  - name: worker
    type: worker
    runtime: docker
    repo: https://github.com/bex-co/bex
    rootDir: examples/stack-demo
    envVars:
      - key: ROLE
        value: worker   # the shared image branches on ROLE (web is the default)
      - key: DATABASE_URL
        fromDatabase:
          name: db
          property: connectionString

databases:
  - name: db
    plan: free
```

The example creates a database, web service, and worker. From a clone of the Bex
repository, follow the [Blueprint authentication and validation steps](./app-resource.md),
then validate and apply `examples/stack-demo/render.yaml`. Review all three resources
and their plans before deploying. The `type: worker` service gets a Deployment with no
Service or Ingress — no port, no readiness probe, no public URL.

## Lifecycle and secrets

Workers use the same verbs as web services — [restart, suspend, and
resume](./app-lifecycle.md) are operational fields that reconcile without a new build.
Configure credentials through [environment variables and secrets](./secrets.md). The
database reference in this example supplies the worker connection string without
committing the credential to Git.

Use the dashboard lifecycle controls or the authenticated API commands in the
[lifecycle guide](./app-lifecycle.md).

## Verify that the worker is doing work

A running worker has no HTTP readiness check. Inspect its [logs](./logging.md)
and verify the application-level outcome: a consumed queue message, updated
row, or completed task. A live deployment does not prove that a queue connection
works or that useful work is being processed.

Design consumers to handle interrupted processing and duplicate delivery.
A deploy, restart, or machine failure can stop a process while it owns a job.
Acknowledge work after it is safely completed, and handle termination so the
worker can stop accepting new work before it exits.

For a downloadable Postgres producer/worker with `SKIP LOCKED` claims, leases,
and inspectable results, see [Durable job workers](./durable-workers.md).

Scaling a worker adds concurrent consumers. Confirm that the queue and task
implementation support that concurrency before increasing the instance count.
If a worker exits immediately, check that its command starts a long-running
consumer rather than a one-off script. If it remains running but idle, check
queue credentials, network access, and the queue name.

## Related pages

- [Which service type to use?](./service-types.md) — compare all service types
- [Cron jobs](./cron-jobs.md) — for scheduled, run-to-completion work
- [Secrets](./secrets.md) — give workers their credentials
- [App lifecycle: restart, suspend, resume](./app-lifecycle.md) — restart or suspend a worker without redeploying
