Jobs
Jobs are finite workloads in Quave ONE. Use them for customer-visible, one-off or repeatable commands such as Rails migrations, database maintenance scripts, data imports, and backfills.
A Job is a first-class app type, separate from Apps, Databases & Services, and Functions. Each execution creates a JobRun with its own status, command snapshot, timing, logs, and exit-code metadata.
How Jobs Work
- Finite execution: Each run creates a Kubernetes Job instead of a long-running Deployment.
- No public host by default: Jobs do not need a port, hosts, HTTP probes, or a health check.
- Visible history: Every run is stored as a
JobRun, so customers can see what command ran, when it started, whether it succeeded, and which logs it produced. - Normal resource usage while running: Job pods consume account resources while they run. Finished Kubernetes Jobs are cleaned up by their TTL.
- Safe config changes: Editing a saved Job command creates pending changes. The new command is not used by default runs until you apply those changes.
Common Use Cases
- Rails or Ruby migrations, for example
bin/rails db:migrateorbundle exec rails db:migrate - One-off scripts, for example
bundle exec rake users:backfill - Data imports or exports
- Maintenance tasks that should not restart an app pod repeatedly when they fail
- Repeatable customer operations that need logs and audit history
Create a Job
Via the Web UI
- Open the account in Quave ONE.
- Go to Apps.
- In the Jobs section, click Add new Job.
- Choose the deployment source, usually Use image for a pre-built image or CLI deployment for source builds.
- Set the Job app name, image or build configuration, region, and environment.
- Open the app settings and configure Job Defaults.
- Open the environment settings and use Job Settings if that environment needs a command or timeout override.
- Deploy the image or build the content before creating the first run.
Via the Public API
Create the app with dockerPreset set to JOB. A Job app does not require port.
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"name": "Rails migrations",
"accountId": "ACCOUNT_ID",
"dockerPreset": "JOB",
"useImage": true,
"image": "ghcr.io/acme/my-rails-app:2026-06-19",
"jobConfig": {
"command": "bin/rails db:migrate",
"shell": true,
"timeoutSeconds": 1800,
"ttlSecondsAfterFinished": 21600,
"backoffLimit": 0
},
"isCliDeployment": true
}' \
https://api.quave.cloud/api/public/v1/app
Then create an environment for that app:
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"accountId": "ACCOUNT_ID",
"appId": "APP_ID",
"name": "staging",
"region": "us-5",
"zClouds": 1
}' \
https://api.quave.cloud/api/public/v1/app-env
If you deploy a pre-built image, deploy it to the environment before running the Job:
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"appEnvId": "APP_ENV_ID",
"image": "ghcr.io/acme/my-rails-app:2026-06-19"
}' \
https://api.quave.cloud/api/public/v1/app-env/deploy-image
Via MCP
Ask your AI agent to create and configure the Job:
"Create a Job app called Rails migrations using image ghcr.io/acme/my-rails-app:2026-06-19 and command bin/rails db:migrate"
Useful MCP tools include:
| Tool | Purpose |
|---|---|
create-app | Create the Job app with dockerPreset: "JOB". |
create-app-env | Create the environment for the Job app. |
deploy-app-env-image | Deploy a pre-built image to the Job environment. |
update-app-env-job-config | Edit environment-level Job settings. |
apply-app-env-changes | Apply pending Job settings to the environment. |
run-app-env-job | Create a new JobRun. |
list-app-env-job-runs | List previous runs. |
get-app-env-job-run | Check one run. |
get-app-env-job-run-logs | Fetch logs for one run. |
cancel-app-env-job-run | Cancel an active run. |
rerun-app-env-job-run | Rerun from a previous run snapshot. |
Job Settings
Saved Job settings are stored in jobConfig.
| Field | Description |
|---|---|
command | Command to execute. Required before a run starts unless each run provides an override. |
args | Optional argument list. If shell is true, arguments can also be included directly in command. |
shell | Runs the command through /bin/sh -lc when true. Default: true. |
workingDir | Optional working directory inside the container. |
timeoutSeconds | Active deadline for a run. Default: 1800 seconds. |
ttlSecondsAfterFinished | Kubernetes TTL after completion. Default: 21600 seconds. Minimum: 300 seconds. |
backoffLimit | Kubernetes Job retry backoff limit. Default: 0. |
maxConcurrency | Maximum active runs for this Job environment. |
allowConcurrentRuns | Allows unlimited active runs when true and maxConcurrency is omitted. Default: false. |
schedule | Reserved for scheduled Jobs. |
scheduleTimeZone | Reserved for scheduled Jobs. |
Per-run overrides only accept the safe execution fields: command, args, shell, workingDir, timeoutSeconds, ttlSecondsAfterFinished, and backoffLimit. Configure concurrency and schedule fields on the app or environment.
Config Hierarchy and Apply Changes
There are four layers of Job configuration:
- App defaults: Set on the Job app as
jobConfig. - Environment overrides: Set on one app environment as
jobConfig. - Applied deployment snapshot: Captured from the app and environment settings when the environment image/build is deployed or when pending Job settings are applied.
- Per-run override: Passed when creating one
JobRun; it only affects that run.
By default, new JobRuns use the applied deployment snapshot. If a Job environment does not have an applied snapshot yet, Quave ONE falls back to the current app and environment settings. This means:
- Editing Job Defaults on the app creates pending deploy changes for the environments of that app.
- Editing Job Settings on the environment creates pending deploy changes for that environment.
- A default run keeps using the previous applied command until you click Apply changes, call
POST /api/public/v1/app-env/apply-changes, call the MCP toolapply-app-env-changes, or save withapplyImmediately: true. - Passing a per-run
jobConfigtorun-app-env-joborPOST /api/public/v1/app-env/job-runsoverrides the applied snapshot for that run only. - Historical
JobRunrecords are immutable. Changing the app or environment config does not rewrite older run snapshots.
Environment tokens can save pending Job config changes when scoped to that environment, but applying changes immediately requires a user token.
Run a Job
Create a run with the saved applied command:
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"appEnvId": "APP_ENV_ID"
}' \
https://api.quave.cloud/api/public/v1/app-env/job-runs
Or override the command for one run:
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"appEnvId": "APP_ENV_ID",
"jobConfig": {
"command": "bin/rails db:migrate",
"timeoutSeconds": 1800,
"backoffLimit": 0
}
}' \
https://api.quave.cloud/api/public/v1/app-env/job-runs
The response includes jobRunId, status, the command snapshot, timing values, and the content version used for the run.
Check Status and Logs
List recent runs:
curl -X GET \
-H 'Authorization: YOUR_TOKEN' \
'https://api.quave.cloud/api/public/v1/app-env/job-runs?appEnvId=APP_ENV_ID&limit=20'
Get one run:
curl -X GET \
-H 'Authorization: YOUR_TOKEN' \
'https://api.quave.cloud/api/public/v1/app-env/job-run?jobRunId=JOB_RUN_ID'
Fetch logs:
curl -X GET \
-H 'Authorization: YOUR_TOKEN' \
'https://api.quave.cloud/api/public/v1/app-env/job-run/logs?jobRunId=JOB_RUN_ID&stream=INFO'
Job statuses are QUEUED, STARTING, RUNNING, SUCCEEDED, FAILED, CANCELED, TIMED_OUT, and NOT_FOUND. Treat only SUCCEEDED as a successful terminal status. FAILED, CANCELED, TIMED_OUT, and NOT_FOUND should block a release pipeline.
Rails Migration Pattern
For a Rails migration:
- Build or publish the same image version that your app should migrate.
- Create a Job app with
dockerPreset: "JOB"and that image. - Set the app default command to the Rails command that exists inside your container, usually
bin/rails db:migratefor apps with Rails binstubs orbundle exec rails db:migrate. - Create one environment per target, for example staging and production.
- Deploy the image to the target Job environment.
- Apply pending Job setting changes if you edited the command after the deploy.
- Run the Job.
- Poll the
JobRununtil it is terminal. - Read the logs and exit status.
Recommended config:
{
"command": "bin/rails db:migrate",
"shell": true,
"timeoutSeconds": 1800,
"ttlSecondsAfterFinished": 21600,
"backoffLimit": 0
}
Use backoffLimit: 0 when a migration should not retry automatically after a failure. If you want to run it again, use rerun-app-env-job-run or create another JobRun after checking the logs.
Gate an App Deploy on a Rails Migration
A Rails release usually needs the migration to finish before the web app rollout continues. In Quave ONE, use the same image tag for the migration Job and the app deploy, then poll the JobRun status before deploying the app.
For a complete GitHub Actions workflow, compare this sequence with the public demo repository quaveone/rails-migration-job-demo.
The API flow is:
- Deploy or register the new image on the migration Job environment.
- Create the JobRun and save the returned
jobRunId. - Poll
GET /api/public/v1/app-env/job-run?jobRunId=JOB_RUN_IDuntilisTerminalis true. - Continue only when
statusisSUCCEEDEDorisSuccessis true. - If the status is
FAILED,CANCELED,TIMED_OUT, orNOT_FOUND, fetch logs and stop the app deploy. - Deploy the web app image after the migration succeeds.
The CLI supports this as a pipeline-friendly sequence:
IMAGE="ghcr.io/acme/rails-app:${GITHUB_SHA}"
quaveone job run \
--env acme-rails-migration-prod \
--image "$IMAGE" \
--command "bin/rails db:migrate" \
--wait \
--timeout-seconds 1800 \
--backoff-limit 0 \
--logs-on-failure
quaveone deploy \
--env acme-rails-web-prod \
--image "$IMAGE" \
--wait
In CI, keep these as separate ordered steps or join them with shell &&. Because quaveone job run --wait exits non-zero for every terminal status except SUCCEEDED, the app deploy step will not run when the migration fails.
Quave ONE can gate the deploy, but Rails migrations should still be written with normal zero-downtime practices: prefer backward-compatible schema changes, avoid assuming all old pods have stopped, and inspect the JobRun logs before retrying a failed migration.
Rerun and Cancel
- Rerun uses the previous run's immutable config snapshot and content by default. You can override
contentIdor provide a new per-runjobConfig. - Cancel deletes the Kubernetes Job for an active run and marks the
JobRunasCANCELED.
Troubleshooting
- The new command did not run: Check for pending changes. Apply them, or pass a per-run
jobConfigoverride. - Job command is required: Set
jobConfig.commandon the app or environment, apply it, or passjobConfig.commandwhen creating the run. - Job image is not ready: Deploy an image or complete a build before running the Job.
- Concurrency limit reached: Wait for the active run to finish, set
maxConcurrency, or enableallowConcurrentRuns. - No logs yet: A run may still be
QUEUEDorSTARTING. Fetch the run status first, then request logs after it reachesRUNNINGor a terminal status.
For the broader product taxonomy, see App Types. For endpoint-level details, see the Job Runs API.