Deploy MkDocs documentation to GitHub Pages with versioning support using mike.
This action deploys MkDocs documentation with automatic version management based on the trigger event:
| Trigger | Version Deployed | Alias |
|---|---|---|
pull_request |
develop |
- |
Push to main |
main |
latest (default) |
release |
Tag version (e.g. v1.2.0) |
Configurable (default: latest) |
- uses: serapeum-org/github-actions/actions/mkdocs-deploy@mkdocs/v1
with:
trigger: ${{ github.event_name }}
deploy-token: ${{ secrets.GITHUB_TOKEN }}| Input | Description | Required | Default |
|---|---|---|---|
trigger |
Deployment trigger type: pull_request, main, release |
Yes | - |
package-manager |
Package manager: pip, uv, pixi |
No | uv |
package-manager-version |
Version of the uv/pixi package manager (forwarded to its version input); ignored for pip. |
No | latest |
python-version |
Python version to install | No | 3.12 |
install-groups |
Dependency groups to install | No | groups: docs |
deploy-token |
GitHub token for deployment | Yes | - |
release-tag |
Release tag version (for release trigger) | No | - |
mike-alias |
Mike alias for releases | No | latest |
notebooks-path |
Root directory of Jupyter notebooks to execute and cache. Empty = skip. | No | `` (skip) |
notebooks-exclude |
Newline-/comma-separated globs (relative to notebooks-path) for notebooks to skip executing. |
No | `` (none) |
notebooks-continue-on-error |
Downgrade a non-excluded notebook execution failure to a warning instead of aborting. | No | false |
Deploy a develop version on every PR for review:
name: Docs Preview
on: pull_request
permissions:
contents: write
jobs:
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: serapeum-org/github-actions/actions/mkdocs-deploy@mkdocs/v1
with:
trigger: 'pull_request'
deploy-token: ${{ secrets.GITHUB_TOKEN }}Deploy on push to main and on release:
name: Deploy Docs
on:
push:
branches: [main]
release:
types: [published]
permissions:
contents: write
jobs:
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: serapeum-org/github-actions/actions/mkdocs-deploy@mkdocs/v1
with:
trigger: ${{ github.event_name == 'release' && 'release' || 'main' }}
deploy-token: ${{ secrets.GITHUB_TOKEN }}
release-tag: ${{ github.event.release.tag_name }}- uses: serapeum-org/github-actions/actions/mkdocs-deploy@mkdocs/v1
with:
trigger: 'main'
deploy-token: ${{ secrets.GITHUB_TOKEN }}
package-manager: 'pixi'
install-groups: 'default'Deploy docs that include executable Jupyter notebooks. The action runs notebooks once, caches their outputs, and reuses the cache on subsequent runs so unchanged notebooks don't re-execute:
- uses: serapeum-org/github-actions/actions/mkdocs-deploy@mkdocs/v1
with:
trigger: ${{ github.event_name }}
deploy-token: ${{ secrets.GITHUB_TOKEN }}
notebooks-path: 'docs/notebook'See Notebook execution below for the consumer contract.
Setting notebooks-path enables a pre-build step that executes Jupyter
notebooks and persists their outputs via actions/cache, so mkdocs-jupyter
can render them without re-executing on every CI run. Omitting
notebooks-path (the default) skips notebook execution entirely — existing
workflows see no change.
-
Dependencies: four packages must be in the dependency group passed via
install-groups, split by role:jupyter,nbconvert,ipykernel— used by the action's notebook execution step. The action runs a preflight import check against these three and fails fast with an actionable error if any are missing.mkdocs-jupyter— used later by the mkdocs build itself to render notebooks. A missingmkdocs-jupyterpasses preflight but fails at build time with an mkdocs plugin error — so add it alongside the other three.
Concrete example (statista's layout; adapt the group name to whatever you pass via
install-groups):# pyproject.toml [dependency-groups] docs = [ "mkdocs>=1.5", "mkdocs-material", "mkdocs-jupyter", # rendering (mkdocs build) "mike", "jupyter", # preflighted; drives nbconvert --execute "nbconvert", "ipykernel", ]
-
mkdocs-jupyter config: set
execute: falseinmkdocs.ymlso the plugin reads pre-executed outputs instead of re-running notebooks during the mkdocs build:plugins: - mkdocs-jupyter: execute: false
-
Project layout:
pyproject.tomlat repo root and library source undersrc/. The cache-invalidation hash covers notebook contents,src/**/*.py, andpyproject.toml; flat-layout projects (library code at repo root rather than undersrc/) are not supported out of the box.
Some notebooks can't execute on a clean runner — they need live network/cloud
data, an optional extra that isn't installed for docs, or are intentionally
flaky. List them in notebooks-exclude (newline- or comma-separated globs,
matched against each notebook's path relative to notebooks-path) to skip
their execution. Excluded notebooks are left untouched and rendered with
whatever outputs they already carry. This mirrors pytest's --ignore-glob.
- uses: serapeum-org/github-actions/actions/mkdocs-deploy@mkdocs/v1
with:
trigger: ${{ github.event_name }}
deploy-token: ${{ secrets.GITHUB_TOKEN }}
notebooks-path: docs/examples
notebooks-exclude: |
dask/*.ipynb
**/lazy-*-complete.ipynb
**/stac-cloud-*.ipynb
zarr/*.ipynbPattern semantics (these mirror pytest's --ignore-glob, which uses
fnmatch):
*matches across/, so a pattern likedask/*.ipynbmatches every notebook underdask/at any depth, not just its direct children — and a bare*.ipynbmatches every notebook in the tree. Anchor the pattern (e.g.dask/, or a distinctive filename stem) to avoid over-excluding.- A leading
**/matches at any depth including the root, so**/stac-cloud-*.ipynbalso matches a top-levelstac-cloud-foo.ipynb. - Matching is case-sensitive on every runner OS (so
Foo.ipynbis not matched byfoo.*).
To keep executing the rest when a non-excluded notebook fails — rather than
aborting the whole deploy — set notebooks-continue-on-error: 'true'. The
failing notebook is logged as a CI warning and is not cached, so it re-runs on
the next CI run; use it for genuinely flaky notebooks, and prefer
notebooks-exclude for ones that can never run in CI.
- uses: serapeum-org/github-actions/actions/mkdocs-deploy@mkdocs/v1
with:
trigger: ${{ github.event_name }}
deploy-token: ${{ secrets.GITHUB_TOKEN }}
notebooks-path: docs/examples
# Never-runs-in-CI notebooks are excluded outright...
notebooks-exclude: |
**/stac-cloud-*.ipynb
# ...while an occasional flaky failure only warns instead of failing.
notebooks-continue-on-error: 'true'The cache key is:
jupyter-cache-<hashFiles(notebooks, src/**/*.py, pyproject.toml)>
Any change to a notebook, to library source under src/, or to
pyproject.toml invalidates the cache and triggers a full re-execute.
There is no restore-keys fallback — primary-key-only, so rendered outputs
always reflect the exact code that produced them.
Because executed outputs are produced in CI, commit notebooks with their
outputs stripped. A pre-commit hook such as
nbstripout is the usual way.
- Sets up Python environment using the selected package manager action
- (If
notebooks-pathis set) Restores.jupyter_cache/fromactions/cache, preflight-checks thatjupyter/nbconvert/ipykernelare importable, and runs the vendoredprep_notebooks.pyscript — which copies cached executed notebooks back into the working tree where available, executes (viajupyter nbconvert --execute --inplace) the rest, and skips any notebook matchingnotebooks-exclude - Configures git with the workflow actor's identity
- Runs the appropriate
mikecommand based on trigger:- pull_request:
mike deploy --push develop - main:
mike deploy --push main && mike set-default --push main - release:
mike deploy --push --update-aliases <tag> latest
- pull_request:
- For non-pip managers, commands are prefixed with
<manager> run
Every mike ... --push runs through a fetch-reset-retry wrapper
(scripts/mike_push.sh): before each attempt it fetches the current
gh-pages tip and repoints the local branch ref to it, so a deploy that loses a
push race (a concurrent deploy, or an out-of-band gh-pages push such as a
history squash) refetches and retries instead of failing with
! [rejected] gh-pages -> gh-pages (fetch first). mike merges versions.json
from the tip it builds on, so retries are idempotent and concurrently-added
versions are preserved. A single, uncontended deploy still publishes exactly
one new gh-pages commit.
A per-ref
concurrency:group in your caller workflow is still worth adding as complementary serialization — it prevents this workflow's own runs from racing, while the retry above also covers out-of-band pushes it can't see.
mkdocsandmikein your project dependencies- GitHub Pages enabled on the repository
contents: writepermission in the workflow- (For notebook execution)
jupyter,nbconvert,ipykernel,mkdocs-jupyterin the dependency group passed viainstall-groups; src-layout project structure