Skip to content

    Scheduling

    Run a plan on a cron schedule, with overlap protection, retries and dependencies. Approval gates apply exactly as they do to a manual run.

    A scheduled task runs a plan on a cron schedule. It carries its own execution mode, variables and environment, so the schedule is a complete description of a recurring run rather than a trigger that needs context from elsewhere.

    Before you begin#

    You need the Operator role or above, and a plan that already runs correctly when started by hand.

    1. Create the schedule#

    Give it a name, the plan to run, a cron expression, and a timezone.

    cron
    0 2 * * *      every day at 02:00
    0 */4 * * *    every four hours
    30 3 * * 0     Sundays at 03:30

    The timezone is stored with the schedule and defaults to UTC. Setting it to the timezone your team actually works in is usually right, and it means a schedule does not drift by an hour twice a year relative to your maintenance window.

    A schedule can be disabled without being deleted, which is what you want during an incident or a freeze.

    2. Set overlap protection#

    By default a schedule will not start if its previous run is still going. The fire is skipped and logged rather than queued, because a backup job that takes longer than its interval should not accumulate copies of itself.

    max_execution_time bounds how long the previous run is allowed to hold that lock, so a genuinely stuck run does not suppress the schedule forever.

    Overlap can be allowed where concurrent runs are genuinely safe. Most of the time they are not, which is why the default is off.

    Retries#

    A failed scheduled run can retry a set number of times, with a delay between attempts.

    Retries are worth it for failures that are plausibly transient: a host that was rebooting, a repository that was briefly unreachable. They are not worth it for a plan that fails because it is wrong, where three attempts produce three identical failures and a noisier log.

    Dependencies#

    A schedule can declare that it runs only after other tasks, with three modes:

    ModeRuns when
    All successEvery named task succeeded
    Any successAt least one succeeded
    All completeAll finished, whether or not they succeeded

    All complete is the one to reach for when a cleanup task should run regardless of what happened upstream. All success is the default and the right choice for a genuine pipeline.

    Approval gates still apply#

    This is worth planning around rather than discovering. Two workable shapes:

    • Schedule against environments that do not require approval, and keep gated environments for changes a person is initiating anyway.
    • Accept the pause, and treat the approvals inbox as the queue it becomes. Reasonable when the schedule is infrequent and someone is expected to review it.

    What does not work is scheduling a gated plan overnight and expecting it to have run by morning. It will be waiting.

    Dry runs on a schedule#

    A schedule can run in dry-run mode, which changes nothing and reports what would happen.

    This is a genuinely useful pattern: a nightly dry run of your production plans is drift detection. If a plan that reported no change for a month starts reporting would change, something moved on those hosts that nobody recorded.

    History#

    Each fire produces a run with its own record, kept even if the schedule is later deleted, so the history of what ran does not disappear with the thing that scheduled it.

    Verify it worked#

    Open Schedules. The new schedule is listed as enabled, with the next fire time resolved in the timezone you set:

    schedules
    nightly-patch    0 2 * * *    Europe/Paris    enabled    next: 2026-09-09 02:00

    Do not wait for the first fire to find out whether it works. Start the plan by hand once and confirm it completes, then check back after the first scheduled run: each fire produces its own run in the run history, with the schedule named as what started it.

    Next steps#