Runnel 2.8: replay any workflow from a failed step.Read the release

Skip to content
Runnel
Documentation

    Docs menu

    Schedules

    Run a job on a cron expression in a time zone, pause and resume schedules, and understand what happens when a schedule overlaps or is missed.

    A schedule runs a job on a timetable: every night at two, every fifteen minutes, the first Monday of the month. Runnel keeps the timetable, so you no longer need a cron server, and every scheduled run is an ordinary run with retries, logs and a status.

    Defining a schedule

    Schedules are declared in code next to the job they run. runnel deploy registers them, and they start firing in that environment straight away.

    src/schedules.ts
    import { runnel } from '@runnel/sdk';
    import { usageReport } from './jobs/usage-report';
    import { syncSupplier } from './jobs/sync-supplier';
    
    runnel.schedule({
      name: 'nightly-usage',
      cron: '0 2 * * *',
      timezone: 'Europe/London',
      job: usageReport,
      payload: { period: 'day' },
    });
    
    runnel.schedule({
      name: 'supplier-sync',
      cron: '*/15 * * * *',
      job: syncSupplier,
      payload: { supplierId: 'sup_atlas' },
    });

    Import schedules.ts from your worker file so the definitions are picked up. One job can have several schedules with different payloads.

    Cron expressions

    Runnel uses the standard five-field syntax: minute, hour, day of month, month, day of week. A few that come up often:

    ExpressionRuns
    */15 * * * *every 15 minutes
    0 2 * * *daily at 02:00
    30 8 * * 1-5weekdays at 08:30
    0 9 1 * *the first of every month at 09:00
    0 0 * * 0Sundays at midnight

    The shorthands @hourly, @daily and @weekly work too. Seconds are not supported; the shortest interval is one minute.

    Time zones

    Without timezone, a schedule runs in UTC. With it, the expression is read in that zone, using IANA names such as Europe/London or America/Chicago. Daylight saving is handled the way people expect:

    • When clocks go forward and a time does not exist (02:30 on the last Sunday of March in London), the run fires at the next valid minute.
    • When clocks go back and a time happens twice, the run fires once, on the first occurrence.

    Tip Reports for people should use their time zone. Housekeeping jobs (cleaning up expired sessions, rotating files) are simpler in UTC, where every day has 24 hours.

    Overlaps and missed runs

    If a run is still going when the next one is due, Runnel starts the next one anyway. For jobs that must not overlap, set overlap: 'skip' and the new run is skipped and logged instead:

    runnel.schedule({ name: 'supplier-sync', cron: '*/15 * * * *', job: syncSupplier, overlap: 'skip', payload: { supplierId: 'sup_atlas' } });

    Schedules fire from Runnel’s side, so a worker that is down does not lose them: the runs wait in the queue until a worker picks them up. If you would rather drop runs that waited too long, add expireAfter: '10m'; queued scheduled runs older than that are cancelled.

    Pausing and resuming

    You can pause a schedule from the dashboard or the CLI without a deploy. A paused schedule fires nothing, and on resume it does not catch up on the runs it skipped.

    runnel schedules list --env production
    runnel schedules pause nightly-usage --env production
    runnel schedules resume nightly-usage --env production

    A paused schedule stays paused across deploys until you resume it. Removing a schedule from your code and deploying deletes it.

    Python and Go

    runnel.schedule(name="nightly-usage", cron="0 2 * * *", timezone="Europe/London",
                    job=usage_report, payload={"period": "day"})
    runnel.NewSchedule("nightly-usage", "0 2 * * *", UsageReport,
    	runnel.InTimezone("Europe/London"), runnel.WithPayload(Period{Kind: "day"}))

    To see when a schedule will next fire, run runnel schedules list; it prints the next three times in the schedule’s own zone. The Jobs API returns the same under each job’s schedules.

    Edit this page