CI/CD for a Jupyter Notebook Project with GitHub Actions
Execute every notebook top to bottom in CI so a stale cell never sneaks into main.
Notebooks rot quietly: a cell that worked last month breaks when a dependency updates, but nobody re-runs it. CI fixes that by executing every notebook end to end and failing if any cell errors. This recipe uses pytest-nbmake to run all notebooks as part of the test suite, with pip caching for the heavy data stack.
What the pipeline does
- Installs the data stack plus pytest-nbmake.
- Executes every notebook top to bottom.
- Fails the build on any cell error.
- Caches pip to keep heavy installs fast.
The workflow
pytest-nbmake runs notebooks as test items, so a single pytest invocation executes and validates them all.
name: Notebook CI
on: [push, pull_request]
jobs:
execute:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
- run: pip install -r requirements.txt pytest nbmake
- name: Execute all notebooks
run: pytest --nbmake notebooks/ --nbmake-timeout=600Notes for this platform
Set a per-notebook timeout so a hung cell fails fast instead of stalling the run. For notebooks that need data, point them at small committed fixtures rather than external downloads to keep CI deterministic. This is Linux-native and belongs on your cheapest, fastest runner class; managed runners auto-retry transient PyPI install failures so a flaky index does not break a notebook run that would otherwise pass.
Key takeaways
- Execute every notebook end to end so stale cells fail in CI, not later.
- pytest-nbmake runs notebooks as test items in one pytest invocation.
- Set a per-notebook timeout so a hung cell fails fast.