Publishing (Material for) MkDocs website to GitHub Pages using custom Actions workflow
As you can probably see, this website is built using the Material theme for MkDocs, which we have been happily using for over one year after using Sphinx for many years prior to that. GitHub Pages offers built-in support for Jekyll, but not for MkDocs and therefore it requires the manual building and deployment of our website. However, it automates many other things, including HTTPS certificate provisioning on our domain via Let's Encrypt.
There are several somewhat related approaches using GitHub Actions for automating the deployment of MkDocs-generated sites, usually with the Material theme, to GitHub Pages. These guides are not only found on blogs written by enthusiasts; the official Getting started section of the Material for MkDocs documentation describes the usage of GitHub Actions for deployment and provides a generic YAML file for that purpose.
Using the approaches mentioned above avoids the requirement to run the build
and gh-deploy
steps locally; GitHub Actions does both on GitHub's CI/CD servers, where the free plan offers 2000 minutes of GitHub-hosted runners per month. As many sites build in less than a minute, this amount allows from 50 to 100 builds and deployments per day, which is quite a bit more than most sites require. Additionally, the repository layout remains the same as it would be if the build and deployment steps were done locally; the main
branch contains the site source in Markdown and the gh-pages
branch contains the site files that get built for serving.
Since this summer, GitHub offers publishing Pages using a custom Actions workflow as a public beta, which was a unique feature of GitLab Pages for years. I thought that it would be interesting to see if we could use the existing GitHub Actions workflow configuration for Jekyll and simply replace the Jekyll build step with the MkDocs build step. This would streamline the usage of MkDocs with GitHub Pages, and, in particular, eliminate the requirement for publishing the site from a separate gh-pages
branch, offering a Jekyll-like experience.
Let's see how far we can get. Without going into details about the syntax for GitHub Actions, here is the starter workflow configuration file for deploying a Jekyll site to GitHub Pages:
# Sample workflow for building and deploying a Jekyll site to GitHub Pages
name: Deploy Jekyll with GitHub Pages dependencies preinstalled
on:
# Runs on pushes targeting the default branch
push:
branches: [$default-branch]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
# Build job
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Build with Jekyll
uses: actions/jekyll-build-pages@v1
with:
source: ./
destination: ./_site
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
The highlighted lines are Jekyll-specific. We can easily replace these lines with:
- the Python setup Action,
- the installation, using pip and the
requirements.txt
file, of- MkDocs with extra internationalization support,
- the Material for MkDocs framework,
- the optional dependencies required for the generation of social cards, that is, CairoSVG, which will pull Pillow as a dependency, and, finally,
- the MkDocs site build command.
In this case, since we want a drop-in replacement for Jekyll so that the remaining commands work perfectly, we will perform the MkDocs build using the mkdocs.yml
configuration file in the current directory and write the built site output files into the _site
directory.
The .github/workflows/mkdocs-gh-pages.yml
file will look like:
# Sample workflow for building and deploying a MkDocs site to GitHub Pages
name: Deploy MkDocs with GitHub Pages dependencies preinstalled
on:
# Runs on pushes targeting the default branch
push:
branches: ["main"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
# Build job
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install yamllint
run: pip install yamllint
- name: Check MkDocs YAML configuration
run: yamllint ./mkdocs.yml
continue-on-error: true
- name: Check Markdown files
uses: DavidAnson/markdownlint-cli2-action@v16
with:
globs: '**/*.md'
continue-on-error: true
- name: Install required packages
run: pip install -r requirements.txt
- name: Build site (_site directory name is used for Jekyll compatiblity)
run: mkdocs build --config-file ./mkdocs.yml --strict --site-dir ./_site
env:
CI: true
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Two linters are used:
-
for
mkdocs.yml
, yamllint configuration is in the.yamllint.yaml
file. It should reside in the root of the repository and contain the following:extends: default rules: document-end: present: false document-start: present: false line-length: level: warning allow-non-breakable-inline-mappings: true
-
for Markdown files, markdownlint configuration is in the
.markdownlint.json
file. It should also reside in the root of the repository and contain the following:{ "default": true, "MD007": { "indent": 4 } }
Finally, we can see the mention of the requirements.txt
file. You guessed it, it should reside in the root of the repository as well. It should contain the following text:
mkdocs-material[recommended,imaging]
And that's it! There is no more requirement for the .nojekyll
file as Jekyll never gets ran in the build process. There is also no more separate gh-pages
branch that the built files get pushed to, so there is also no more worry whether the site builds over time will add up to the 1 GB soft limit.
Finally, if you want to use a custom domain, having the CNAME
file in the repository root or the docs
subfolder will no longer have the desired effect; the domain has to be configured through the repository settings or using the API.
Updated on 2022-11-25: changed Python version from 3.10 to 3.11, resulting in faster docs builds (see Faster CPython for details).
Updated on 2022-12-03: changed caching to use github.sha
instead of github.ref
, enabling rebuilds of social cards when site contents change.
Updated on 2023-06-06: rebased our additions on top of the latest version of jekyll-gh-pages.yml
from Starter Workflows. Changed Python version from 3.11 to the latest stable 3.x, which is 3.11 at the moment. However, using the current beta version of Python 3.12 already works well with mkdocs-material
, so it's unlikely to cause issues even when 3.12 gets released and becomes the latest stable version.
Updated on 2023-09-08: simplified the workflow to use the existing requirements.txt
file instead of duplicating the package names in the pip
command run.
Updated on 2023-10-24: updated requirements.txt
to use the extras for the installation of the optional dependencies.
Updated on 2023-12-28: bumped Actions versions by rebasing our additions on top of the latest version of jekyll-gh-pages.yml
from Starter Workflows.
Updated on 2024-05-12: added yamllint and markdownlint steps. Removed caching as (Material for) MkDocs version is not pinned and therefore the site builds are not reproducible.