Skip to content

πŸ“˜ Installing MkDocs-Material

MkDocs

Material is a custom theme for the MkDocs static site generator that's geared towards building project documentation. Documentation source files are written in Markdown, and configured with a single YAML configuration file

Info

Both projects are open-source and can be downloaded here: https://github.com/squidfunk/mkdocs-material.


πŸ“₯ Installation

πŸ“‹ Requirements

Info

MkDocs requires the installation of


🐳 Install MkDocs

The use of Docker Compose will automate the installation of MkDocs container.

πŸ”§ Setup MkDocs Parameters

Before deploying, you need to define a few environment variables that will be used throughout the setup process.

  • HOST_PORT: external port used by NGINX to route traffic to the service
  • GIT_REPOS: path to the local git repository
# example of configuration for environment parameters
GIT_REPOS=/path/to/git-repos.git
HOST_PORT=10000
βš™οΈ Configure MkDocs for Docker Compose

MkDocs can be deployed using Docker Compose. The compose.yml file will automatically incorporate the environment variables configured in the previous step. You can copy, paste, and run all of the following commands directly in your terminal.

# create docker directory
mkdir mkdocs && cd mkdocs
mkdir build
# setup of compose.yml
tee compose.yml > /dev/null <<'EOF'
services:
  mkdocs-docs:
    build:
      context: ./build
      additional_contexts:
        - repo=${GIT_REPOS}
      args:
        - PUID=${PUID}
        - PGID=${PGID}
    image: mkdocs-docs:latest
    container_name: mkdocs-docs
    user: "${PUID}:${PGID}"
    volumes:
      - ${GIT_REPOS}:/git:ro
    ports:
      - ${HOST_PORT}:8000
    healthcheck:
      test: ["CMD", "curl", "-fsS", "-o", "/dev/null", "http://localhost:8000/"]
      start_period: 180s
      start_interval: 5s
      interval: 60s
      timeout: 1s
      retries: 3
    restart: unless-stopped
EOF
# setup of .env file
tee .env > /dev/null <<EOF
###################################################################################
# Run as non-root user
###################################################################################
PUID=`id -u`
PGID=`id -g`

###################################################################################
# NGINX Proxy Configuration
###################################################################################
HOST_PORT=${HOST_PORT}

###################################################################################
# MkDocs Configuration
###################################################################################
GIT_REPOS=${GIT_REPOS}
EOF

Keep the .env file

All the secret informations will be stored in the .env file.

🐳 Create the custom MkDocs-Material docker container

To create a custom Docker container for Mkdocs-Material based on python, follow these steps:

  1. Configure the requirements.txt File
    Modify the requirements.txt file to specify the required MkDocs plugins that will be installed in the Docker container.

    # setup requirements.txt file
    tee build/requirements.txt > /dev/null <<EOF
    mkdocs-material
    mkdocs-awesome-pages-plugin
    mkdocs-minify-plugin
    mkdocs-macros-plugin
    mkdocs-mermaid2-plugin
    EOF
    
  2. Create the run.sh Script
    Develop a run.sh script that will be executed upon container startup.
    This script should perform the following actions:

    • πŸ“ Check if a Git repository needs to be cloned.
    • ⬇️ Fetch the latest commits from the Git repository.
    • 🧹 Use git reset --hard to navigate to the latest commits, handling cases where the commit history has been rewritten.
    • βš™οΈ Build the MkDocs-material documentation if necessary.
    • 🌐 Launch a Python HTTP server to serve the static website created.
    # create run.sh script
    tee build/run.sh > /dev/null <<'EOF'
    #!/bin/sh
    # Update + build only when the Git HEAD changed.
    # The build hash is stored in .env ONLY if the build succeeds.
    
    set -u  # error on undefined variables
    
    #############################################
    # Configuration
    #############################################
    LOGS="/tmp/update-logs.log"
    RETRY_DELAY=60
    ENV_FILE=".env"
    BRANCH="master"
    
    #############################################
    # Helpers
    #############################################
    log_step() {
      printf "=== %s: " "$1"
    }
    
    fail_with_logs() {
      echo "failed ==="
      echo "---- logs ----"
      cat "$LOGS"
      echo "--------------"
      echo "$1"
      sleep "$RETRY_DELAY"
      exit 1
    }
    
    get_current_hash() {
      git rev-parse --short HEAD
    }
    
    get_previous_hash() {
      if [ -f "$ENV_FILE" ]; then
        grep -E '^LATEST_COMMIT=' "$ENV_FILE" 2>/dev/null | head -n1 | cut -d= -f2-
      else
        echo ""
      fi
    }
    
    store_hash() {
      new_hash="$1"
      if [ -f "$ENV_FILE" ] && grep -q '^LATEST_COMMIT=' "$ENV_FILE" 2>/dev/null; then
        sed -i "s|^LATEST_COMMIT=.*|LATEST_COMMIT=$new_hash|" "$ENV_FILE"
      else
        printf "\nLATEST_COMMIT=%s\n" "$new_hash" >> "$ENV_FILE"
      fi
    }
    
    run_cmd() {
      "$@" > "$LOGS" 2>&1
      return $?
    }
    
    #############################################
    # Main Script
    #############################################
    # Update repository
    log_step "git pull"
    run_cmd git pull origin "$BRANCH" --rebase
    if [ $? -ne 0 ]; then
      fail_with_logs "git pull failed"
    fi
    echo "succeed ==="
    echo ""
    
    # Compare hashes
    current_hash="$(get_current_hash)"
    previous_hash="$(get_previous_hash)"
    if [ "$current_hash" != "$previous_hash" ]; then
      echo "hash change -> building"
      echo ""
    
      # Build only when hash changed
      log_step "building mkdocs-material documentation"
      rm -rf ./site
      run_cmd mkdocs build --no-directory-urls
      if [ $? -ne 0 ]; then
        fail_with_logs "build failed (hash NOT updated)"
      fi
      echo "succeed ==="
      echo ""
    
      # Add apple-touch-icon, android and manifest files
      if [ -f "./site/index.html" ]; then
        log_step "adding apple/android/manifest icons files"
        sed -i '/<link rel="icon" href="assets\/images\/favicon.png">/d' "./site/index.html"
        sed -i '20i\
            <link rel="manifest" href="/app.webmanifest">\
            <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico">\
            <link rel="icon" type="image/png" href="/favicon.png">\
            <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">\
        ' ./site/index.html
        echo "succeed ==="
        echo ""
      fi
    
      # Store the hash ONLY if build succeeded
      store_hash "$current_hash"
    else
      echo "no hash change -> skipping build"
      echo ""
    fi
    
    # Launch python http server
    echo "running python http-server on kouizine site directory..."
    run_cmd python -m http.server 8000 -d ./site
    if [ $? -ne 0 ]; then
      fail_with_logs "python http-server failure"
    fi
    EOF
    
  3. Create the Dockerfile:
    Create a Dockerfile that includes the following steps:

    • Copy the requirements.txt file into the Docker container.
    • Copy the run.sh script into the Docker container.
    # setup of Dockerfile
    tee build/Dockerfile > /dev/null <<'EOF'
    #############################################
    ##### Builder stage: install Python deps
    #############################################
    FROM python:3.12-slim AS builder
    
    # work dir for building Python dependencies
    WORKDIR /tmp
    
    # install required MkDocs plugins and extensions
    COPY requirements.txt /tmp/requirements.txt
    RUN set -eux; \
        pip install --no-cache-dir --upgrade pip; \
        pip install --no-cache-dir --prefix=/install -r /tmp/requirements.txt
    
    #############################################
    ##### Final stage: runtime image
    #############################################
    FROM python:3.12-slim
    
    # configuration variables
    ARG PUID=1000
    ARG PGID=1000
    
    # container paths
    ENV APP_DIR=/docs
    ENV RUN_SCRIPT=/usr/src/app/run.sh
    
    # install base runtime tools
    RUN set -eux; \
        apt-get update; \
        apt-get install -y --no-install-recommends \
          git \
          curl \
          tini \
        ; \
        rm -rf /var/lib/apt/lists/*
    
    # copy installed Python packages from builder stage
    COPY --from=builder /install /usr/local
    
    # non-root user setup
    RUN set -eux; \
        groupadd --gid "${PGID}" debian; \
        useradd  --uid "${PUID}" --gid "${PGID}" --shell /bin/bash --create-home debian; \
        mkdir -p "${APP_DIR}"; \
        chown -R debian:debian "${APP_DIR}"; \
        chmod 2775 "${APP_DIR}"
    
    # runtime configuration
    USER debian
    WORKDIR ${APP_DIR}
    
    # clone directory from a local Git context
    COPY --from=repo / /git
    RUN set -eux; \
        git config --global --add safe.directory /git; \
        git clone /git .
    USER root
    RUN rm -rf /git
    USER debian
    
    # copy the runtime script that updates repos, builds docs, and starts the server
    COPY --chown=${PUID}:${PGID} run.sh ${RUN_SCRIPT}
    RUN chmod 0755 ${RUN_SCRIPT}
    
    # runtime configuration
    EXPOSE 8000
    ENTRYPOINT ["/usr/bin/tini", "--", "/usr/src/app/run.sh"]
    EOF
    
🐳 Install MkDocs with Docker Compose

Now that the compose.yml file has been generated, it's time to install all the containers.

# install and start the container
docker compose up --build -d

πŸš€ Deploy MkDocs

Install NGINX

NGINX needs to be installed, follow the NGINX section.

Configure NGINX

NGINX needs to be configured using a file in /etc/nginx/sites-enabled directory.
This configuration file specify the documentation path:

server {
  server_name mkdocs.domain.fr;

  # setup 404 error_page
  error_page 404 /404.html;
  include snippets/error-404.conf;

  # reverse proxy
  location / {
    proxy_pass http://127.0.0.1:10000;

    # keep it HTTP/1.1
    proxy_http_version 1.1;

    # forwarded headers
    include proxy_params;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Port $server_port;

    # timeouts for long-running sessions
    proxy_read_timeout 600s;
    proxy_send_timeout 600s;
  }
}
# restart nginx
sudo nginx -t && sudo service nginx restart

Replace mkdocs.domain.fr by the name of your website.

Activate HTTPS

To activate HTTPS protocol, follow theΒ Let's Encrypt section.


πŸͺ Create Git hook script

The following git hook script automatically generates the MkDocs static site whenever a git push is made.

# create post-update hook in git repository
tee post-update > /dev/null <<EOF
echo "=== restarting docker container: mkdocs-docs ==="
docker restart mkdocs-docs
echo "=== done ===\n"
exit 0
EOF

Important

Follow the Git Hook section and add this content in the script section.

Add the git user to docker group

To enable the ability to restart the mkdocs-docs container using the docker command, it is essential for the post-update script to access the file located at /var/run/docker.sock. This necessitates the inclusion of the user git to the docker group.

# add git user to the docker group
sudo usermod -aG docker git