Scheduled CI jobs and nightly pipelines
The nightly build is the most fragile job you own — it only runs when you're asleep, so it breaks in silence. Keep the cron in your runner, but make the work a workflow you can run by hand and always get a report from.
blog/scheduled-nightly-pipelineThe nightly pipeline is the job you trust the least. It only runs at 2am, so when it breaks it breaks in silence, and you find out three days later that the last green nightly was Tuesday. The logic is usually trapped inside a scheduled CI definition you can't run on your laptop — so debugging a nightly means pushing a commit, waiting for the cron, and reading job logs the next morning.
The split that fixes this: let your CI runner own the schedule, but put the work in a workflow you can run by hand. The cron becomes a one-liner that calls OrchStep; everything it does is local, runnable, and previewable.
The work the schedule triggers
name: nightly
# The pipeline a nightly schedule calls. The cron lives in your CI runner;
# this is the work it triggers, plus a summary that always runs.
defaults:
run_id: "nightly"
tasks:
# `orchstep run nightly`
nightly:
steps:
- name: fetch
func: shell
do: echo "pulling latest main for the {{ vars.run_id }} build"
- name: build
func: shell
do: echo "building the {{ vars.run_id }} artifact"
- name: regression
func: shell
do: echo "running the full regression suite"
retry:
max_attempts: 2
interval: "1s"
- name: report
func: shell
do: echo "{{ vars.run_id }} pipeline complete"
finally:
- name: summary
func: shell
do: echo "posting the {{ vars.run_id }} summary to the team channel"The win is in the last step. Its finally: block runs whether the regression passed or blew up — so the team channel gets a summary every single night. A nightly that fails silently is the dangerous one; a nightly that always reports, pass or fail, is just information.
- name: report
func: shell
do: echo "{{ vars.run_id }} pipeline complete"
finally:
- name: summary
func: shell
do: echo "posting the {{ vars.run_id }} summary to the team channel"The regression step gets a small retry so a single flaky run doesn't page anyone, and fetch / build / regression are your real commands once you swap out the echo.
The schedule is a one-liner
The cron stays where crons belong — in the CI runner. It installs OrchStep and calls the task. Here's the shape on GitHub Actions:
name: nightly
on:
schedule:
- cron: "0 2 * * *" # 02:00 UTC every day
jobs:
nightly:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Manual install alternative:
# - run: curl -fsSL https://orchstep.dev/install.sh | sh
# - run: orchstep run nightly
- uses: orchstep/run-orchstep@v1
with:
workflow: orchstep.yml
task: nightlyThat's the entire integration. The schedule triggers; OrchStep does the work. Because the work is a plain task, the thing that runs at 2am is the same thing you run at your desk:
orchstep run nightlyNo more "I can't reproduce the nightly." You are the nightly when you run that line. And before you trust it on a schedule, dry-run it to confirm the steps and order:
orchstep run nightly --dry-runWhat you gained
| Concern | Cron-only job | OrchStep |
|---|---|---|
| Run it locally | push and wait | orchstep run nightly |
| Always get a report | silent on failure | finally: summary |
| Flaky regression | red nightly, pager | small retry: |
| Reuse the steps | trapped in CI YAML | a task you can call |
| Reschedule | edit CI logic | change the cron line |
OrchStep isn't a scheduler and doesn't try to be — your runner's cron is fine. It's the runnable, reportable body that the schedule calls, so the most fragile job you own stops being a black box.
Where to go next
- Continuous Integration — install and run OrchStep in any CI
- Error Handling —
finally:and retries - Functions: shell — wrap your existing build and test commands
Install with curl -fsSL https://orchstep.dev/install.sh | sh, then orchstep run nightly to run tonight's pipeline right now.
curl -fsSL https://orchstep.dev/install.sh | sh