A notebook that only works on one person's machine, in one kernel session, is not a result anyone can trust.
Reproducibility problems come from three sources: state, environment and files.
State. Out-of-order execution and deleted cells leave variables that the notebook no longer defines. Restart and Run All before sharing, and make CI run jupyter nbconvert --execute (or papermill) so broken notebooks fail fast. Set random seeds explicitly.
Environment. Record dependencies with pinned versions (requirements.txt, uv.lock, environment.yml) and use a dedicated kernel per project. Print key versions in the first cell when results matter, and avoid absolute paths to your home directory; use paths relative to the project.
Files and Git. .ipynb files are JSON that store outputs, execution counts and metadata, so diffs are noisy and images bloat history. Options:
- nbstripout installs a Git filter that removes outputs on commit (
nbstripout --install). - Jupytext pairs each notebook with a plain-text script, for example
py:percentformat with# %%cell markers. Review and diff the.pyfile; Jupytext keeps both in sync when you save. Many teams commit only the text version. - nbdime provides notebook-aware
nbdiffandnbmerge, and Git integration vianbdime config-git --enable.
Keep heavy logic in importable modules with tests, and let notebooks orchestrate and explain. A notebook that is mostly calls to well-named functions is easy to rerun, review and trust.
Example
# Pair a notebook with a percent-format script and keep them in sync
jupytext --set-formats ipynb,py:percent analysis.ipynb
jupytext --sync analysis.ipynb # updates whichever is older
head -3 analysis.py
# # ---
# # jupyter:
# # jupytext: (YAML header, then cells marked with # %%)
# Strip outputs automatically on commit
pip install nbstripout && nbstripout --install
# Notebook-aware diffs and merges in git
nbdime config-git --enable
git diff analysis.ipynb # now shows cell-level changes
# Prove it runs from scratch (e.g. in CI)
jupyter nbconvert --to notebook --execute analysis.ipynb --output /tmp/ci.ipynbRemember
- Restart and Run All, and execute notebooks in CI to catch hidden state
- Pin the environment and use relative paths and explicit seeds
- Use nbstripout, Jupytext and nbdime to make notebooks Git-friendly
Watch out for
- Committing notebooks with large outputs, so every rerun produces a huge, unreadable diff.