Deploy a cron job with YAML

A cron job is a container that runs to completion on a schedule, then exits. Use it for nightly batches, periodic cleanups, hourly imports — anything that runs on a clock instead of continuously.

Updated 23 Jun 20263 min read

A cron job is a container that runs to completion on a schedule, then exits. Use it for nightly batches, periodic cleanups, hourly imports — anything that runs on a clock instead of continuously.

This guide walks through the cron job YAML shape. For the flag-mode equivalent, see Deploy a cron job with flags.

Minimal example

kind: container
project: my-project
handle: nightly-reports
name: Nightly Reports
image: ghcr.io/myorg/reports:v1.0.0
type: cronjob
regions:
  - falkenstein-1
 
workload:
  cpu: "100"
  memory: "256"
  command: [/usr/bin/php]
  args: [artisan, reports:nightly]
 
schedule:
  schedule: "0 2 * * *"
  tz: Europe/London

Apply it:

reis apply -f nightly-reports.yml

That cron job runs every night at 2am London time.

How cron jobs differ from workers

Same kind: container, same commands, but with key differences:

  • A schedule is required — the cron expression under schedule.schedule is the only thing that distinguishes a cron job from a never-running worker.
  • Runs to completion — when your command exits successfully, the run is recorded as Succeeded; on non-zero exit it's Failed. The cron job itself is always around, waiting for the next tick.
  • No autoscaling, no port, no health check — none of these apply.
  • Concurrency policy controls what happens if a previous run is still going when the next tick fires.

Anatomy

kind: container
project: my-project
handle: nightly-reports
name: Nightly Reports
image: ghcr.io/myorg/reports:v1.0.0
type: cronjob
registry: ghcr
regions:
  - falkenstein-1
 
workload:
  cpu: "100"
  memory: "256"
  env:
    REPORT_BUCKET: prod-reports
  secrets:
    DB_PASSWORD: db-password
    S3_KEY: s3-key
  command:
    - /usr/bin/php
  args:
    - artisan
    - reports:nightly
    - --send-email
 
schedule:
  schedule: "0 2 * * *"              # 5-field cron — 02:00 daily
  tz: Europe/London                  # IANA timezone the schedule fires in
  concurrency: Forbid                # Allow, Forbid (default), or Replace
  backoff_limit: 3                   # retries before a run is considered failed
  ttl_seconds: 3600                  # keep finished run records for N seconds
  successful_history: 3              # how many successful runs to keep
  failed_history: 3                  # how many failed runs to keep

Concurrency policy

What happens at tick T+1 if the run from tick T is still running?

PolicyBehaviour
AllowStart the new run alongside the old one
Forbid (default)Skip the new tick — wait for the next
ReplaceKill the running one and start fresh

Pick Forbid for jobs where double-running would corrupt data; Replace for jobs where freshness beats completeness.

Execution settings

YAML pathWhat it controls
schedule.schedule5-field cron expression — required
schedule.tzIANA timezone (default UTC)
schedule.concurrencyconcurrency policy
schedule.backoff_limitretries per execution (default 3)
schedule.active_deadlinehard wall-clock cap per run, in seconds
schedule.ttl_secondsseconds to keep finished run records (default 3600)
schedule.starting_deadlineskip a run if more than N seconds late
schedule.successful_historyhow many successful runs to keep (default 3)
schedule.failed_historyhow many failed runs to keep (default 3)

Manual runs and pausing

Once the cron job is deployed, the container:run, container:suspend, container:resume, and container:runs commands let you trigger ad-hoc runs, pause the schedule, and inspect run history. See the commands reference for details.

Updating a cron job

Re-apply the same file:

reis apply -f nightly-reports.yml

Reis matches by handle. Common edits — changing the schedule, the image, the retries — all happen the same way.