Skip to content

MkDocs Deploy Action

Deploy MkDocs documentation to GitHub Pages with versioning support using mike.

Overview

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)

Usage

- uses: serapeum-org/github-actions/actions/mkdocs-deploy@mkdocs/v1
  with:
    trigger: ${{ github.event_name }}
    deploy-token: ${{ secrets.GITHUB_TOKEN }}

Inputs

Input Description Required Default
trigger Deployment trigger type: pull_request, main, release Yes -
package-manager Package manager: pip, uv, pixi No uv
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

Examples

Pull Request Preview

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 }}

Production Deployment

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 }}

With pixi

- uses: serapeum-org/github-actions/actions/mkdocs-deploy@mkdocs/v1
  with:
    trigger: 'main'
    deploy-token: ${{ secrets.GITHUB_TOKEN }}
    package-manager: 'pixi'
    install-groups: 'default'

With notebook execution

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.

Notebook execution

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.

Requirements

  • 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 missing mkdocs-jupyter passes 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: false in mkdocs.yml so the plugin reads pre-executed outputs instead of re-running notebooks during the mkdocs build:
plugins:
  - mkdocs-jupyter:
      execute: false
- Project layout: pyproject.toml at repo root and library source under src/. The cache-invalidation hash covers notebook contents, src/**/*.py, and pyproject.toml; flat-layout projects (library code at repo root rather than under src/) are not supported out of the box.

Excluding notebooks that can't run in CI

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/*.ipynb

Pattern semantics (these mirror pytest's --ignore-glob, which uses fnmatch):

  • * matches across /, so a pattern like dask/*.ipynb matches every notebook under dask/ at any depth, not just its direct children — and a bare *.ipynb matches 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-*.ipynb also matches a top-level stac-cloud-foo.ipynb.
  • Matching is case-sensitive on every runner OS (so Foo.ipynb is not matched by foo.*).

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'

Cache invalidation

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.

Stripping notebook outputs on commit

Because executed outputs are produced in CI, commit notebooks with their outputs stripped. A pre-commit hook such as nbstripout is the usual way.

How It Works

  1. Sets up Python environment using the selected package manager action
  2. (If notebooks-path is set) Restores .jupyter_cache/ from actions/cache, preflight-checks that jupyter/nbconvert/ipykernel are importable, and runs the vendored prep_notebooks.py script — which copies cached executed notebooks back into the working tree where available, executes (via jupyter nbconvert --execute --inplace) the rest, and skips any notebook matching notebooks-exclude
  3. Configures git with the workflow actor's identity
  4. Runs the appropriate mike command based on trigger:
  5. pull_request: mike deploy --push develop
  6. main: mike deploy --push --update-aliases main latest && mike set-default --push latest
  7. release: mike deploy --push --update-aliases <tag> latest
  8. For non-pip managers, commands are prefixed with <manager> run

Prerequisites

  • mkdocs and mike in your project dependencies
  • GitHub Pages enabled on the repository
  • contents: write permission in the workflow
  • (For notebook execution) jupyter, nbconvert, ipykernel, mkdocs-jupyter in the dependency group passed via install-groups; src-layout project structure