# Run a job on a schedule

A job is a service that runs a command and exits, instead of staying up. It is the same kind of thing as a runtime service — same repository or image, same variables, same build — with one setting changed: its mode is **scheduled** rather than persistent.

Because a job is not serving anything between runs, it has none of the settings that only make sense for something that is: no port, no replicas, no scale to zero, no health checks, and no address you can reach it at. What it has instead is a schedule, a command, a time budget, and a history of the times it ran.

Use a job for the work that happens on a clock rather than on a request: a nightly export, a fifteen-minute reconciliation, a weekly report.

Where each action lives:

| Surface | Job actions |
| --- | --- |
| Product App | **Add service** on the environment canvas creates one; the job becomes a node, and clicking it opens its panel, with **Run now**, **Pause** and the run history on it. |
| MCP | An agent creates a job with [`deployment_service_create`](/reference/mcp-tools/deployment_service_create) and changes it with [`deployment_service_update`](/reference/mcp-tools/deployment_service_update). Starting a run, listing the history and reading a run's logs are each available over MCP too; the [MCP tool catalog](/reference/mcp-tools) is the list of what your agent can call. |

## Create the job

**Add service** on the environment canvas, then set the mode to scheduled. You give it a source the same way as any other service, plus:

- the **command** it should run — a shell command, up to 4096 characters. Leave it empty to run the image's own entrypoint;
- the **schedule** it should run on, or none at all if you only ever want to start it yourself.

Over MCP an agent calls [`deployment_service_create`](/reference/mcp-tools/deployment_service_create) with the mode set to scheduled, and gives it the same source, schedule and command.

The mode is fixed when you create the service. A job cannot be turned into a runtime service later, or the other way round — create the one you want. Everything else is editable afterwards with [`deployment_service_update`](/reference/mcp-tools/deployment_service_update).

## Write the schedule

A schedule is a five-field cron expression, **always in UTC**:

```text
minute  hour  day-of-month  month  day-of-week
```

| Schedule | When it runs |
|---|---|
| `0 3 * * *` | every day at 03:00 UTC |
| `*/15 * * * *` | every fifteen minutes |
| `0 9 * * 1` | Mondays at 09:00 UTC |
| `0 0 1 * *` | the first of the month, at midnight UTC |
| `30 6 * * 1-5` | weekdays at 06:30 UTC |

There are no time zones. A schedule written for 09:00 local time will drift by an hour when your country changes its clocks, so write the UTC time you actually mean.

An invalid expression is refused when you save it, not at the moment it would have run.

### A job with no schedule

A job may have no schedule at all. It is then a job you start by hand: it never fires on its own, but everything else about it works — the command, the variables, the history, the logs.

This is also what you get while you are still setting one up. Create the job without a schedule, run it a few times until the command does what you want, then give it a schedule.

## Run one now

**Run now** on the job's panel starts a run immediately. It ignores the schedule and it ignores the concurrency setting: if you ask for a run, you get one, even if the last one is still going.

A manual run is recorded in the history the same way a scheduled one is, marked as manual so you can tell them apart.

The job has to have been deployed at least once first — until then there is nothing to run.

## Pause and resume

**Pause** stops a job firing without losing its schedule; **Resume** starts it again. Over MCP this is a field on [`deployment_service_update`](/reference/mcp-tools/deployment_service_update).

Pausing does not touch a run that is already going, and it does not affect **Run now** — a paused job can still be started by hand.

A paused job stays paused across deploys, and a forked environment reproduces the pause, so forking an environment you had deliberately quietened does not start it firing in the copy. See [Work with environments](/ship/deployment/environments).

## Set the time budget and what overlaps do

**Timeout** is the wall-clock budget for one run, between 60 seconds and 24 hours, one hour by default. A run that is still going when the budget runs out is stopped, and it is recorded as timed out rather than failed — those are different things, and the history says which happened.

**Concurrency** decides what happens when the next tick arrives and the previous run has not finished:

- **forbid** — the default. The tick is skipped and recorded as skipped, so the gap in the history has an explanation. The running job is left alone.
- **allow** — the new run starts alongside the old one.
- **replace** — the running one is stopped and the new one takes its place.

If you are unsure, keep **forbid**. It is the setting that cannot produce two copies of the same work at once.

## Read the history and the logs

Every run is recorded: when it started, when it finished, whether it succeeded, its exit code, and the output it produced. The job's panel lists them newest first, and over MCP the same list comes back a page at a time.

A run is in one of five states:

| Status | What it means |
|---|---|
| Running | started, not finished yet |
| Succeeded | finished with exit code 0 |
| Failed | finished with a non-zero exit code |
| TimedOut | stopped because it exceeded its timeout |
| Skipped | the tick never fired — concurrency was set to forbid and the previous run was still going |

**Logs** on a run gives you its output: a live tail while the run is going, and the stored copy once it has finished. The stored copy is the **last 256 KiB** — the end of the output, where a failure usually is, not the beginning.

The history keeps the **last 100 runs of a job, and nothing older than 30 days**. A skipped tick is a row like any other and counts towards that hundred. If you need runs kept for longer than that, send what matters somewhere durable from inside the job itself.

## What a run costs

A run is billed like any other compute: for the resources it uses while it runs, and for nothing between runs. A job that runs for two minutes a night costs two minutes a night.

If a product is suspended over billing, its jobs stop firing along with everything else, and **Run now** is refused. Clearing the suspension gives the schedules back — including the ones you had paused yourself, which stay paused.

## Limits

- **No automatic retries.** A failed run is recorded as failed and the job waits for its next tick. If a piece of work must be retried, retry it inside the command.
- **UTC only.** Schedules have no time zone, and none can be set.
- **No ports, no domains, no replicas.** A job is not reachable over the network; nothing can call it, and there is nothing to scale.
- **The mode is fixed at creation.** A job cannot become a runtime service, or the other way round.
- **No shell into a job.** There is no running container between runs to attach to. To run something one-off against a job's image, use the separate-run mode of a one-off command — see [Run a command inside a service](/ship/deployment/terminal) — or start a real run with **Run now**.

## Next steps

- [Set up a service](/ship/deployment/services): the runtime service and static site a job shares its source, build and variables with.
- [Set variables and secrets](/ship/deployment/variables): what the command reads from its environment.
- [Deploy, redeploy and roll back](/ship/deployment/deploy): a job has to be deployed once before it can run at all.
- [Run a command inside a service](/ship/deployment/terminal): the one-off command, and why only its separate-run mode works against a job.
