How to build a documentation portal with MkDocs and GitLab Pages

I wrote detailed documentation for the popular openvpn-install script — covering everything from quick start to script internals. Here's how I built the documentation portal using MkDocs Material, GitLab Pages, and CI/CD with pinned dependencies.

The result is a fast, versioned, searchable documentation site with dark mode, code highlighting, Mermaid diagrams, and zero backend. Every push to main triggers an automatic build and deploy.

What the documentation covers

The docs are structured into logical sections: installation and quick start, client and server management, CLI reference, network/DNS/firewall configuration, security and encryption settings, advanced topics like fingerprint authentication and Data Channel Offload, and a deep dive into every part of the script's internals — from OS detection to the interactive menu system.

Directory structure

├── docker-compose.yml
├── .gitlab-ci.yml
├── mkdocs.yml
├── overrides/
└── docs/
    ├── index.md
    ├── compatibility.md
    ├── installation.md
    ├── quickstart.md
    ├── testing.md
    ├── faq.md
    ├── css/
    │   └── extra.css
    ├── media/
    │   └── logo/
    │       ├── openvpn.svg
    │       └── openvpn-favicon.svg
    ├── usage/
    │   ├── client-management.md
    │   ├── server-management.md
    │   └── uninstall.md
    ├── configuration/
    │   ├── cli-reference.md
    │   ├── network-dns.md
    │   ├── security.md
    │   └── firewall.md
    ├── advanced/
    │   ├── fingerprint-auth.md
    │   ├── dco.md
    │   └── customization.md
    └── script-internals/
        ├── overview.md
        ├── logging.md
        ├── validation.md
        ├── os-detection.md
        ├── network-detection.md
        ├── installation-process.md
        ├── client-operations.md
        ├── server-operations.md
        └── interactive-menu.md

mkdocs.yml — key parts

The full config uses Material theme with custom branding, light/dark mode toggle, sticky navigation tabs, instant loading, and a rich set of Markdown extensions including Mermaid diagram support.

site_name: OpenVPN Install Docs

nav:
  - "Overview":
    - "Introduction": index.md
    - "Compatibility": compatibility.md
  - "Getting Started":
    - "Installation": installation.md
    - "Quick Start": quickstart.md
  - "Usage":
    - "Client Management": usage/client-management.md
    - "Server Management": usage/server-management.md
    - "Uninstall": usage/uninstall.md
  - "Configuration":
    - "CLI Reference": configuration/cli-reference.md
    - "Network & DNS": configuration/network-dns.md
    - "Security & Encryption": configuration/security.md
    - "Firewall": configuration/firewall.md
  - "Advanced":
    - "Fingerprint Auth": advanced/fingerprint-auth.md
    - "Data Channel Offload": advanced/dco.md
    - "Customization": advanced/customization.md
  - "Script Internals":
    - "Overview": script-internals/overview.md
    - "Logging System": script-internals/logging.md
    - "Validation & Parsing": script-internals/validation.md
    - "OS Detection": script-internals/os-detection.md
    - "Network Detection": script-internals/network-detection.md
    - "Installation Process": script-internals/installation-process.md
    - "Client Operations": script-internals/client-operations.md
    - "Server Operations": script-internals/server-operations.md
    - "Interactive Menu": script-internals/interactive-menu.md
  - "Testing": testing.md
  - "FAQ": faq.md

theme:
  name: material
  custom_dir: overrides
  logo: media/logo/openvpn.svg
  favicon: media/logo/openvpn-favicon.svg
  features:
    - navigation.instant
    - navigation.instant.prefetch
    - navigation.tabs
    - navigation.tabs.sticky
    - navigation.sections
    - navigation.top
    - search.suggest
    - search.highlight
    - content.code.copy
    - content.code.select
  palette:
    - media: "(prefers-color-scheme: light)"
      scheme: default
      toggle:
        icon: material/weather-night
        name: Switch to dark mode
    - media: "(prefers-color-scheme: dark)"
      scheme: slate
      toggle:
        icon: material/weather-sunny
        name: Switch to light mode

markdown_extensions:
  - admonition
  - pymdownx.details
  - pymdownx.superfences:
      custom_fences:
        - name: mermaid
          class: mermaid
          format: !!python/name:pymdownx.superfences.fence_code_format
  - pymdownx.highlight:
      anchor_linenums: true
  - pymdownx.tabbed:
      alternate_style: true
  - pymdownx.emoji:
      emoji_index: !!python/name:material.extensions.emoji.twemoji
      emoji_generator: !!python/name:material.extensions.emoji.to_svg
  - toc:
      permalink: true

plugins:
  - search
  - minify:
      minify_html: true

.gitlab-ci.yml

All Python dependencies are pinned to exact versions for reproducible builds. The pipeline uses rules instead of the deprecated only keyword, and artifacts expire after 1 hour to save storage.

image: python:3.13

stages:
  - deploy:static

pages:
  stage: deploy:static
  script:
    - pip install mkdocs==1.6.1
    - pip install mkdocs-material==9.7.6
    - pip install mkdocs-material-extensions==1.3.1
    - pip install mkdocs-minify-plugin==0.8.0
    - pip install mkdocs-redirects==1.2.2
    - mkdocs build --site-dir public
  artifacts:
    expire_in: 1 hrs
    paths:
      - public
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'

docker-compose.yml — local preview

For local development, the same pinned dependencies are installed inside a Python container. Run docker compose up and open http://localhost:8000 to preview with live reload.

services:
  mkdocs:
    image: python:3.13
    volumes:
      - .:/docs
    working_dir: /docs
    ports:
      - "8000:8000"
    command: >
      bash -c "pip install
      mkdocs==1.6.1
      mkdocs-material==9.7.6
      mkdocs-material-extensions==1.3.1
      mkdocs-minify-plugin==0.8.0
      mkdocs-redirects==1.2.2
      && mkdocs serve -a 0.0.0.0:8000"

Result

Push to main, wait for the pipeline, and the site is live. No manual deploys, no servers to maintain.

Human Logic, AI Syntax... Note on Content: I'm a Systems Engineer, not a native English writer. To ensure my technical ideas are clear and accessible, I use AI tools to polish the grammar and style. The workflow is simple: I provide the logic, the code, and the real-world experience. The AI handles the "English-to-Human" translation layer. If you find a bug, that's on me. If you find a perfectly placed comma, that's probably the AI.

Comments

Popular posts from this blog

FreeRadius with Google Workspace LDAP

Fixing pssh (parallel-ssh) Problems on Debian 10 with Python 3.7