# Jobs

Your own commands, run on rollout steps, on a schedule or on demand.

```yaml
jobs:
  migrate:  { service: web, command: npm run migrate, on: deploy }
  seed:     { service: web, command: npm run seed, on: seed }
  warm:     { service: web, command: npm run warm-cache, on: start }
  digest:   { service: worker, command: npm run digest, on: { schedule: "0 8 * * *" } }
  backfill: { service: worker, command: npm run backfill }
```

A job runs `command` in a fresh container of `service`, with its image and env. `on` is one event or a list.

| `on` | When it runs |
| --- | --- |
| `deploy` | When a sandbox is first set up, and on every update after (a changed manifest, new code, a retry). Migrations go here. |
| `seed` | Once, when a sandbox is first set up. |
| `start` | Once per sandbox, after `deploy` and `seed`. |
| `destroy` | When the sandbox is deleted. |
| `{ schedule: "<cron>", time_zone: <zone> }` | On a five-field cron schedule, or `@hourly`, `@daily`, `@weekly`, `@monthly`, `@yearly`. UTC unless `time_zone` names an IANA zone such as `Europe/Paris`. **Paused in sandboxes:** `parity run <job>` fires one. |
| omitted | Only on demand, with `parity run <job>`. |

- **Order.** Events are phases: all `deploy` jobs, then `seed`, then `start`. A seed never meets an empty schema. Services take traffic once they're done.
- **Parallel unless ordered.** Jobs of one phase run in parallel (up to 8 at once). A job that must run after another lists it in `depends_on`, as in Buildkite. See [Steps](#steps).
- **Failures.** A failed job skips the jobs that depend on it, lets running ones finish, then fails the sandbox; `parity up` prints its log tail. Rerunning `parity up` retries it.
- **Jobs wait** for every managed entry. Services start after the setup jobs, so a job that needs one (a database you run as a service, such as MySQL) lists it: `depends_on: [mysql]`. That service, and the services it depends on, then start before the setup jobs, and the job waits until its first port accepts connections (and its `health` check passes, if it has one). The job's variables still come from its own service. A service keeps no data across restarts, and a sandbox whose seed writes to one isn't restored from a snapshot.
- **In-app schedulers.** Pausing covers the manifest's jobs only. A scheduler inside your own process keeps running. `parity scale <service> 0` stops it.
- **HTTP jobs.** `command: { http: GET /api/cron/digest }` sends that request to the service's first port, instead of starting a container of it. It fails on an HTTP error status, and retries while the service isn't answering yet. If the service has `CRON_SECRET`, the request carries `Authorization: Bearer <CRON_SECRET>`, as Vercel cron does, so existing cron routes work unchanged. HTTP jobs run on a `schedule` or with `parity run`, since setup jobs run before services start.
- **Commands run through a shell when they need one.** A command with `$VAR`, `&&`, quotes or pipes runs as `sh -c`, so `$CRON_SECRET` is the service's variable. A plain `npm run x` runs directly.

## Runners [#runners]

```yaml
runners:
  tools: { image: ghcr.io/acme/deploy-tools:3 }

jobs:
  purge: { service: web, runner: tools, command: ./scripts/purge-cdn.sh, on: deploy }
```

A runner is an image that jobs run on instead of their service's: build tools and CLIs the app itself doesn't need. Define it once and pick it with `runner:`. The job still belongs to its service, so it gets that service's env and egress. The console's Runners page lists each runner, its jobs, and the hosts they were blocked from.

## Steps [#steps]

```yaml
jobs:
  init-db: { label: Create the database, service: web, command: npm run db:init, on: deploy }
  migrate: { service: web, command: npm run migrate, depends_on: [init-db] }
  assets:  { service: web, runner: tools, command: ./scripts/upload-assets.sh, on: deploy }
  release:
    block: Release to customers
    prompt: Check the canary dashboard and #support first.
    depends_on: [migrate, assets]
```

Every job on `deploy`, `seed` or `start` is a step of a sandbox's rollout. Your [environments](/manifest/environments) run the `deploy` steps.

- **`label`** is the name the console and CLI show: one line, up to 200 characters. The job's name stays its key.
- **`depends_on`** lists jobs as well as services. A job runs after the jobs it lists; jobs with nothing between them run in parallel. A job without `on` runs on the latest phase of the jobs it depends on. It can depend on jobs of the same or an earlier phase, never a later one or itself. Only steps depend on jobs: a scheduled or on-demand job can't.
- **`block`** makes a step that runs nothing and waits for a person to approve it: `block: <label>`, or `block: true` with an optional `label`. `prompt` is optional text, up to 1,000 characters, that the approve dialog shows. A block always runs on `deploy`, so every job it depends on must be a `deploy` step.
- **Sandboxes pass blocks.** A sandbox is an agent's, and agents never approve. Environments wait on a block for a person (`environments.deploy`, which Developers have); `environments: [production]` limits it to the environments it names. Approve lets the steps after it run; Reject fails the rollout; if nobody decides within `timeout` (a duration such as `4h`; default `24h`, at most `720h`), the rollout fails. A newer rollout asks again.

How steps run, fail and report status: [Steps and approvals](/concepts/steps).
