Skip to content
Latchkey

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.

.github/workflows/notebook-ci.yml
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=600

Notes 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.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →