---
id: platform/one-off-jobs
title: One-off jobs
description: Run a standalone command from a deployed Bex service image, inspect its status, and understand which service settings are not inherited.
keywords: [bex, one-off jobs, standalone commands, Kubernetes Job]
last_updated: 2026-09-23
---

A one-off job runs a command in a separate container using an existing service's
image. Use it for a finite task whose tools and required files are already in
that image. It is distinct from a [scheduled cron job](./cron-jobs.md), a
[pre-deploy command](./pre-deploy-commands.md), or an
[SSH session](./ssh.md) in a running service.

## Check the execution environment first

The current Bex job implementation copies the service's resolved image, falling
back to its configured image if no resolved image is available. It does **not**
copy the service's environment variables, secret files, persistent disks,
registry pull credentials, or CPU/memory requests into the job. The container
still has any environment and files built into its image.

This differs from Render's service-configuration inheritance. Do not use a
service's working database connection as proof that a one-off job can run its
migration command. Do not put credentials in `startCommand`: commands are saved
in job history. Private images may fail to pull without separately available
cluster credentials.

The API accepts and records `planId`, but the current job creation path does not
apply it as container resource settings. Namespace policies may impose their own
limits. Do not use that field as a resource-sizing guarantee.

Your installation needs configured control-plane storage (`BEX_CP_DB_URI`) and
Kubernetes access. Without that storage, the job API returns `503`. Creating a
job requires developer or admin access; viewing requires resource-read access,
and cancellation requires operate access. See [workspace roles](./members.md).

## Create a job

Use an [API bearer token](./api.md), the API URL for your installation, and a
service ID returned by Bex. The image must be available to the cluster and
contain `sh` and the command's dependencies.

Start with a harmless command to check the execution path:

```bash
curl --fail-with-body --silent --show-error \
  -X POST "$BEX_API_URL/v1/services/$SERVICE_ID/jobs" \
  -H "Authorization: Bearer $BEX_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"startCommand":"printf \"one-off job ready\\n\""}'
```

Save the returned job `id` as `JOB_ID`. The response includes `serviceId`,
`startCommand`, `planId`, `status`, and timestamps as they become available.
A `201` response is not proof of execution: job creation can return a record
already marked `failed` if Kubernetes creation failed.

## Observe completion

```bash
curl --fail-with-body --silent --show-error \
  "$BEX_API_URL/v1/services/$SERVICE_ID/jobs/$JOB_ID" \
  -H "Authorization: Bearer $BEX_TOKEN"
```

Poll periodically, with a timeout appropriate to the task, until `status` is
`succeeded`, `failed`, or `canceled`. `pending` and `running` are not terminal. List and get operations
refresh nonterminal status from Kubernetes, but a temporary cluster error can
leave the last known status visible.

List history with `GET /v1/services/{id}/jobs`. It returns an array of
`{job, cursor}` entries. Use the returned cursor for subsequent pages. Supported
filters include `status`, `createdBefore`, `createdAfter`, `startedBefore`,
`startedAfter`, `finishedBefore`, `finishedAfter`, and `limit`.

Bex creates jobs with no automatic retry and no explicit job execution deadline.
Finished Kubernetes jobs are eligible for cleanup after one hour; this is not a
promise of one hour of application-log retention. An installation administrator
can inspect the job and pod logs in the service's namespace. Do not assume the
service log endpoint includes one-off job output.

## Cancel or retry deliberately

```bash
curl --fail-with-body --silent --show-error \
  -X POST "$BEX_API_URL/v1/services/$SERVICE_ID/jobs/$JOB_ID/cancel" \
  -H "Authorization: Bearer $BEX_TOKEN"
```

Cancellation requests deletion of the Kubernetes job and marks its record
`canceled`. Verify that the pod and any external work have stopped before
starting a replacement.
Canceling an already terminal job returns `409`. Cancellation does not reverse
external writes that the command already made.

A retry is a new job. Inspect partial effects before creating it, and make the
task safe to repeat. For failures, distinguish an unavailable job service, a
missing image, an image-pull failure, a missing shell/tool, and a command failure.
Use [audit logs](./audit-logs.md) to investigate who requested an operation;
verify its actual result in job status and in the system the command changed.
