Guide
Run Jupyter Notebooks Headlessly in CI: A Practical Guide with nbconvert --execute
Run Jupyter notebooks headlessly in CI with nbconvert's --execute flag. This guide covers prerequisites, command options, validation checks, and recovery steps for reliable non‑interactive execution.
Published by Tasadduq Burney
03 Dec 2025, 14:36 UTC
3 min86.3K views0

Desired Outcome
Execute a Jupyter notebook non‑interactively, capture all outputs and tracebacks in a new notebook file, and use the exit status to gate continuous‑integration (CI) jobs.
Prerequisites
- Jupyter & nbconvert – Installed in the same virtual environment that will run the CI job. Verify with
jupyter nbconvert --version. - Kernel registration – The notebook’s
kernelspec(e.g.,python3) must be available:jupyter kernelspec listshould list it. - File paths – The notebook and any data files it references must be reachable from the working directory where the command runs. Relative paths resolve against the notebook’s location.
- Permissions – The CI runner must have read/write access to the notebook, output directory, and any dependent resources.
Procedure
Verify the installed tools:
The output should show a recent nbconvert (≥ 6.0) and the expected kernel.jupyter nbconvert --version jupyter kernelspec listCreate a minimal test notebook (
test.ipynb) containing:
This notebook will serve as a sanity check.# test.ipynb print("Hello, world!")Run the notebook headlessly:
Where:jupyter nbconvert \ --to notebook \ --execute \ --output test_executed.ipynb \ --ExecutePreprocessor.timeout=600 \ test.ipynb--to notebookkeeps the Jupyter format;--executeruns all cells;--outputnames the resulting file;--ExecutePreprocessor.timeoutsets a 10‑minute timeout per cell.Validate the result:
- Check exit code:
echo $?should be0if all cells succeeded. - Open
test_executed.ipynband confirm the first cell’soutputsarray contains the stringHello, world!. - If a cell fails, the traceback is embedded in its
outputsand the process exits with a non‑zero status.
- Check exit code:
Integrate into CI (example GitHub Actions step):
This step will fail the job if any notebook throws an error.- name: Execute notebooks run: | jupyter nbconvert \ --to notebook \ --execute \ --output-dir ./executed \ --ExecutePreprocessor.timeout=600 \ **/*.ipynb continue-on-error: falseOptional: allow errors but still record tracebacks:
The command continues past failing cells, but the exit status remains non‑zero.jupyter nbconvert \ --to notebook \ --execute \ --allow-errors \ --output-dir ./executed \ my_notebook.ipynb
Expected Checks
- Non‑zero exit status indicates a cell raised an exception. Use
--allow-errorsif you want to proceed. - Presence of output notebook in the specified directory.
- Each cell’s
outputsarray should contain either output data or a traceback. - Verify that the kernel used matches the notebook’s
kernelspecby inspecting themetadata.kernelspecfield.
Recovery Options
- Increase timeout – If a cell hangs, raise
--ExecutePreprocessor.timeoutor set to-1to disable the limit. - Debug mode – Add
--debugto get verbose logs that include the exact cell causing failure. - Parameter injection – For notebooks that need input values, consider papermill instead of nbconvert.
- Sandboxing – Run the command in a container or virtual machine to avoid executing arbitrary code on the host.
Limitations
- nbconvert executes the notebook as-is; it does not support parameter passing or environment variable injection beyond what the notebook already uses.
- Execution state is not preserved across runs – each cell starts from a clean kernel.
- Untrusted notebooks will run arbitrary code with the CI runner’s privileges; always review or sandbox before execution.
Quick Comparison: nbconvert vs Papermill
| Feature | nbconvert | papermill |
|---|---|---|
| Parameter injection | No | Yes (via --parameters) |
| Execution mode | Headless, single run | Headless, with parameterization |
| Output format | Notebook (.ipynb) | Notebook (.ipynb) |
| Exit status on error | Non‑zero | Non‑zero (unless --allow-errors) |
| Typical use case | CI gating, batch runs | Parameterized reports, pipelines |
Practical Verification Checklist
- Run
jupyter nbconvert --helpto confirm flag names in your version. - Create a notebook with a deliberate error (e.g.,
raise ValueError("test")) and ensure the exit code is non‑zero. - Measure execution time of a long cell; if it exceeds 30 seconds, adjust
--ExecutePreprocessor.timeout. - After CI integration, inspect the job log for the
CellExecutionErrormessage if a failure occurs.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.