| date | 2026-06-07 |
|---|---|
| slug | mkdocs-github-pages-migration |
| summary | Docs hosting moved to GitHub Pages at modern-di.modern-python.org. |
| outcome | Docs hosting moved to GitHub Pages at modern-di.modern-python.org. |
Date: 2026-06-07 Status: Approved — ready for implementation plan
Serve modern-di documentation at https://modern-di.modern-python.org from GitHub Pages instead of Read the Docs. Single rolling version, no /latest/ URL prefix. Build runs in GitHub Actions on pushes to main that touch docs sources.
Current setup:
- MkDocs Material site, sources in
docs/, config inmkdocs.yml. - Built by Read the Docs via
.readthedocs.yaml, with a custom domain (modern-di.modern-python.org) pointing at RTD. - RTD serves under a
/latest/path prefix (its version routing). One in-code URL embeds that prefix:modern_di/errors.py:32inPROVIDER_DUPLICATE_TYPE_ERROR. - DNS for
modern-python.orgis at a registrar (not Cloudflare).
The migration moves the same MkDocs site to GitHub Pages, with a flat URL structure (no /latest/).
- URL paths: Drop
/latest/. Serve at root. Update the one in-code reference inerrors.py. No redirects. - Cutover: DNS flip directly from RTD to GitHub Pages. No pre-flip verification on the github.io URL or a preview subdomain (user accepted the small-window risk; rollback is a 5-minute DNS revert).
- Read the Docs config: Leave
.readthedocs.yamlin place. RTD keeps buildingmodern-di.readthedocs.ioas a passive fallback. A follow-up PR deletes it once GH Pages is trusted. - Pending
.online → .orglink edits: Roll into the migration PR rather than committing separately. - Deploy mechanism:
mkdocs gh-deploy --force(force-push built site to agh-pagesbranch). Chosen for simplicity and easy local rescue over the Actions-nativeupload-pages-artifact/deploy-pagesflow. - Versioning: None. No
mikeplugin. Single rolling version.
name: Deploy docs
on:
push:
branches: [main]
paths:
- "docs/**"
- "mkdocs.yml"
- "docs/requirements.txt"
- ".github/workflows/docs.yml"
workflow_dispatch:
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -r docs/requirements.txt
- run: mkdocs gh-deploy --forcefetch-depth: 0 is required because gh-deploy needs full history to push to gh-pages. --force overwrites the gh-pages branch each run. Listing docs/requirements.txt under paths: is redundant (covered by docs/**) but kept for visibility.
Already has site_url: https://modern-di.modern-python.org in the working tree (uncommitted). Keep as-is. No further changes.
Drop /latest/ from the troubleshooting URL embedded in PROVIDER_DUPLICATE_TYPE_ERROR:
"See https://modern-di.modern-python.org/troubleshooting/duplicate-type-error/ for more details"Add a local-deploy escape hatch:
docs-deploy:
uv run mkdocs gh-deploy --forceAlready updated to .org in the working tree (uncommitted). Keep as-is.
Performed by the user after merging the PR.
- Merge & push. First push to
maintriggers the workflow, which creates thegh-pagesbranch and pushes the built site. - GitHub repo Settings → Pages
- Source: Deploy from a branch
- Branch:
gh-pages// (root) - Custom domain:
modern-di.modern-python.org(writes aCNAMEfile togh-pages) - Enforce HTTPS: tick once the certificate is provisioned (a few minutes after DNS resolves)
- DNS at registrar
- Remove the existing
modern-diCNAME pointing at RTD - Add CNAME: host
modern-di→ valuemodern-python.github.io.
- Remove the existing
- Wait. DNS propagation + Let's Encrypt provisioning: typically 5–30 minutes.
- Sanity check. Load
https://modern-di.modern-python.org, verify navigation, click through the troubleshooting link emitted fromerrors.py.
.readthedocs.yaml— left in place; deleted in a follow-up PR.docs/context7.json— still points at themodern-di_readthedocs_iosource on context7. Update via the context7 dashboard separately when desired.- Documentation versioning (
mike) — explicitly out of scope.
- Workflow run for
.github/workflows/docs.ymlcompletes green. git ls-remote --heads origin gh-pagesshows the new branch.- After DNS swap,
dig modern-di.modern-python.org CNAME +shortreturnsmodern-python.github.io.. curl -I https://modern-di.modern-python.org/returns 200 with a valid certificate.- The troubleshooting URL emitted by
errors.pyresolves to the live troubleshooting page.
- DNS-level: revert the
modern-diCNAME to RTD's target. Recovery in ~5 minutes once DNS propagates. - Repo-level (if reverting permanently): delete the workflow, delete the
gh-pagesbranch (git push origin --delete gh-pages), re-revert the link edits if desired. RTD config is untouched, so RTD resumes serving the custom domain immediately.
- DNS flip is destructive. No pre-flip verification means a misconfigured CNAME target or missing GH Pages custom-domain entry leaves the subdomain broken until corrected. Rollback path is the DNS revert above.
- Old
/latest/external links 404. Previously released package versions still embed the/latest/URL in theirerrors.py. Out-of-repo links (blog posts, etc.) likewise break. Accepted; no redirect layer added. gh-pagesbranch creation. The repo currently has nogh-pagesbranch, so the firstgh-deploy --forcecreates it cleanly. If agh-pagesbranch is created out-of-band before merge, this assumption breaks.
- Delete
.readthedocs.yaml. - Update
docs/context7.jsononce the docs are re-indexed at the new domain.