Skip to content

Schedule a job

workloads:
nightly-dump:
role: job
image: postgres:17
command: ["sh", "-c", "pg_dump \"$POSTGRES_URL\" | gzip > /backups/$(date -u +%F).sql.gz"]
data_effect: none
needs: [postgres]
volumes: [{name: backups, path: /backups}]
schedule: {cron: "0 2 * * *", timezone: Europe/Berlin}

A schedule is translated into a timer on the host. It fires without any Onebox process running and survives a reboot. There is no daemon, no listening port, and nothing to keep alive.

That design decision has a visible consequence elsewhere: encrypted environment entries are decrypted into the release when it is staged and stay there, because a timer firing at 02:00 must resolve the values the deploy resolved.

0 2 * * * ✓ every day at 02:00
0 2 * * 1 ✓ every Monday at 02:00
0 2 1 * 1 ✗ schedule_untranslatable

The last one declares a day-of-month and a day-of-week. Cron treats that as “either matches” — it fires on the 1st and on every Monday. Onebox refuses it at load rather than running on days nobody chose.

timezone takes an IANA zone name and defaults to UTC.

data_effect is required on every job, scheduled or not. A nightly report is none; a nightly prune is destructive. The rollback and abort gates read it, and a job that lies about it defeats them.

Terminal window
ob job plan nightly-dump --out ob-job-plan.json
ob approve --plan ob-job-plan.json --out ob-job-approval.json
ob job run --plan ob-job-plan.json --approval ob-job-approval.json

ob job plan accepts a job only when its resolved when is manual, which is the default for a job that declares none. A pre_release or post_release job is refused: it belongs to the deploy graph, and one-shot invocation is reserved for the jobs that do not.

A declared when: manual job remains in the digest-pinned release runtime but never joins the deploy graph. Its job plan binds the current serving release, runtime digest, immutable image, data effect, target, and expiry. Automation supplies the saved plan and its separately recorded local confirmation; migration jobs may also need the exact plan-bound backup report.

For an undeclared emergency command, the escape hatch remains:

Terminal window
ob exec --reason "incident investigation" <workload|service> -- <command>

For a job that participates automatically in a deploy, choose a release phase:

when: pre_release # or post_release; manual is invoked only through job plan/run

schedule and when are orthogonal, and both fire. A job declaring schedule: and when: pre_release runs on the host timer at its cron time and again on every deploy. Declaring both is how you ask for both; for the timer alone, leave when at its manual default.

See workloads for every field.