How to create reproducible builds

Your documentation depends on a number of dependencies to be built. If your docs don’t have reproducible builds, an update in a dependency can break your builds when least expected, or make your docs look different from your local version. This guide will help you to keep your builds working over time, so that you can focus on content.

Use a .readthedocs.yaml configuration file

We recommend using a configuration file to manage your documentation. Our config file provides you per version settings, and those settings live in your Git repository.

This allows you to validate changes using pull requests, and ensures that all your versions can be rebuilt from a reproducible configuration.

Use a requirements file for Python dependencies

We recommend using a Pip requirements file or Conda environment file to pin Python dependencies. This ensures that top-level dependencies and extensions don’t change.

A configuration file with explicit dependencies looks like this:

.readthedocs.yaml
version: 2

build:
  os: "ubuntu-22.04"
  tools:
    python: "3.12"

# Build from the docs/ directory with Sphinx
sphinx:
  configuration: docs/conf.py

# Explicitly set the version of Python and its requirements
python:
  install:
    - requirements: docs/requirements.txt
docs/requirements.txt
# Defining the exact version will make sure things don't break
sphinx==5.3.0
sphinx_rtd_theme==1.1.1
readthedocs-sphinx-search==0.1.1

Tip

Remember to update your docs’ dependencies from time to time to get new improvements and fixes. It also makes it easy to manage in case a version reaches its end of support date.

Pin your transitive dependencies

Once you have pinned your own dependencies, the next things to worry about are the dependencies of your dependencies. These are called transitive dependencies, and they can upgrade without warning if you do not pin these packages as well.

We recommend pip-tools to help address this problem. It allows you to specify a requirements.in file with your top-level dependencies, and it generates a requirements.txt file with the full set of transitive dependencies.

βœ… Good:

All your transitive dependencies are defined, which ensures new package releases will not break your docs.

docs/requirements.in
sphinx==5.3.0
docs/requirements.txt
#
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
#    pip-compile docs.in
#
alabaster==0.7.12
    # via sphinx
babel==2.11.0
    # via sphinx
certifi==2022.12.7
    # via requests
charset-normalizer==2.1.1
    # via requests
docutils==0.19
    # via sphinx
idna==3.4
    # via requests
imagesize==1.4.1
    # via sphinx
jinja2==3.1.2
    # via sphinx
markupsafe==2.1.1
    # via jinja2
packaging==22.0
    # via sphinx
pygments==2.13.0
    # via sphinx
pytz==2022.7
    # via babel
requests==2.28.1
    # via sphinx
snowballstemmer==2.2.0
    # via sphinx
sphinx==5.3.0
    # via -r docs.in
sphinxcontrib-applehelp==1.0.2
    # via sphinx
sphinxcontrib-devhelp==1.0.2
    # via sphinx
sphinxcontrib-htmlhelp==2.0.0
    # via sphinx
sphinxcontrib-jsmath==1.0.1
    # via sphinx
sphinxcontrib-qthelp==1.0.3
    # via sphinx
sphinxcontrib-serializinghtml==1.1.5
    # via sphinx
urllib3==1.26.13
    # via requests

Check list βœ…οƒ

If you followed this guide, you have pinned:

  • tool versions (Python, Node)

  • top-level dependencies (Sphinx, Sphinx extensions)

  • transitive dependencies (Pytz, Jinja2)

This will protect your builds from failures because of a random tool or dependency update.

You do still need to upgrade your dependencies from time to time, but you should do that on your own schedule.

See also

Configuration file v2 (.readthedocs.yaml)

Configuration file reference

Build process overview

Build process information

Build process customization

Customizing builds to do more