Right, so you’ve done the hard part: you’ve written beautiful docstrings, wrestled Sphinx into submission, and generated a slick set of static HTML. Now you have a _build/html directory sitting on your machine. Great. You could email it to everyone, I suppose. Or you could host it properly so it’s just a URL anyone can visit, gets updated on every commit, and generally makes you look like a professional. Let’s do that.

The undisputed champion for this is Read the Docs. It’s free for open source projects, deeply integrated with the Python ecosystem, and frankly, it Just Works™ about 95% of the time. The other 5% will have you muttering under your breath, but we’ll get to that.

Why Read the Docs is Your New Best Friend

Think of RTD as your dedicated documentation butler. You connect it to your project’s repository (GitHub, GitLab, etc.), tell it where your config file is, and from that moment on, it does this: every time you push a new commit, RTD will check out your code, install your project (or just its docs dependencies), and run sphinx-build or mkdocs build for you. It then hosts the resulting HTML for the world to see.

The magic is in the automation. You never have to manually build and upload docs again. This is crucial because if it’s manual, it’s outdated. It also handles versioning for you. Push a tag like v2.1.0 to your repo, and RTD will automatically build and host that version’s docs alongside your latest “rolling” development docs.

Setting Up: The 30-Second Overview

  1. Create an account on readthedocs.org, logging in with your GitHub/GitLab/Bitbucket account is the easiest path.
  2. Click “Import a Project.” It will show you a list of your repositories. Find yours and click it.
  3. That’s… often literally it.

No, seriously. If your project has a docs/ folder with a Sphinx conf.py or an mkdocs.yml file in its root, RTD’s default settings will probably find it and build it correctly. But “probably” is a dirty word here, so let’s make it definite.

The .readthedocs.yaml File: Taking Control

Relying on RTD’s auto-discovery is for amateurs and the deeply lucky. You are neither. You’ll create a .readthedocs.yaml file in your root directory to explicitly tell the butler how to do his job. This is the single most important step for avoiding “but it worked on my machine!” headaches.

Here’s a robust example for a Sphinx project:

# .readthedocs.yaml
version: 2

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

sphinx:
  configuration: docs/conf.py

python:
  install:
    - method: pip
      path: .
      extra_requirements:
        - docs
  system_packages: true

Let’s break down why this is gold:

  • version: 2: Always use the new configuration format. The v1 format is legacy for a reason.
  • We pin the OS and Python version. This is critical for reproducible builds. You’re eliminating a whole class of “it worked last Tuesday” errors.
  • sphinx.configuration: Points directly to our conf.py. No guesswork.
  • The python.install section is the genius part. It installs your actual package (path: .) using pip. This means any modules you’re trying to autodoc (e.g., automodule::) will actually be available to Sphinx during the build! The extra_requirements bit tells it to also install the [docs] extra from your setup.py or pyproject.toml, which should contain Sphinx, your theme, and any other docs-specific dependencies.

For a MkDocs project, the config is even simpler:

# .readthedocs.yaml for MkDocs
version: 2

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

mkdocs:
  configuration: mkdocs.yml

python:
  install:
    - requirements: docs/requirements.txt

The Pillars of Success: requirements.txt and Your Dependencies

Notice how both configs explicitly handle dependencies? This isn’t a suggestion. Your build will fail in confusing ways without it. You must isolate your documentation dependencies.

For Sphinx, the best practice is to use an optional dependency in your pyproject.toml:

# pyproject.toml (using setuptools)
[project.optional-dependencies]
docs = [
    "sphinx>=7.0.0",
    "furo>=2023.0",
    "sphinx-autobuild>=2021.0"
]

For MkDocs, a simple docs/requirements.txt file is perfectly standard:

# docs/requirements.txt
mkdocs>=1.5.0
mkdocs-material>=9.0.0
mkdocs-gen-files>=0.4.0

This explicitness is what makes your RTD build reproducible and reliable. Never, ever rely on RTD’s default installed packages. They change, and then your build breaks.

Common Pitfalls and the Art of Debugging

So your build failed. Welcome to the club. The first thing you do is go to your project page on RTD, click “Builds”, and click on the failed build to see the raw log. Scroll to the bottom and read upwards until you see the red error text.

The “ModuleNotFoundError” during autodoc: This is the classic. It means Sphinx tried to import your package to generate docs from it, but couldn’t. This is exactly why we used python.install in our config to install the package itself. If you’re getting this, RTD isn’t finding your package. Double-check that config.

The “Theme Missing” error: You specified a fancy third-party theme (like furo or mkdocs-material) but didn’t include it in your dependency list. RTD doesn’t have it. Add it to your docs extra or your requirements.txt.

The “Build Took Longer Than 15 Minutes” failure: RTD has a time limit. If you have a massive project, you might hit it. You can try to optimize by excluding unnecessary files from the build process using a build.tools setting, but this is a tricky one.

The “It’s still showing the old version!” problem: The RTD build process caches things. Give it a minute. If it’s really stuck, go to the RTD admin for your project and click “Wipe” under the Version tab for the offending version. This nukes the build environment’s cache and forces a truly clean build on the next commit. It solves a surprising number of bizarre issues.

Ultimately, getting RTD configured correctly is a one-time investment that pays endless dividends in credibility and time saved. Do it right once, and your docs will always be there, up-to-date, making you look brilliant.