Skip to content

MkDocs: Project Documentation from Markdown

Documentation in a repository ages faster than anyone reads it: README links lead nowhere, sections are scattered across docs/, wiki/, and Confluence, and site search doesn’t work. MkDocs solves this predictably — it takes a folder of .md files and builds a static site. One config, one command for the deploy, familiar Markdown.

What is MkDocs

MkDocs is a static site generator for documentation written in Python. Input: a directory of Markdown files and a YAML config. Output: a ready site/ directory with HTML, served by any web server or hosted on GitHub Pages, GitLab Pages, S3. MkDocs core handles rendering; the theme defines look and features. The de-facto standard is Material for MkDocs.

Note

MkDocs does not use Jinja templates and does not require a database. It is static content that builds locally or in CI in a few seconds.

Installation

The minimum requirement is Python 3.8+. Install into a virtual environment to avoid polluting the system pip.

python3 -m venv .venv
source .venv/bin/activate
pip install mkdocs

Verification:

mkdocs --version

Typical output is mkdocs, version 1.6.x. The version matters because themes and plugins often require a specific range.

Useful packages installed alongside the core or separately:

PackagePurpose
mkdocs-materialMaterial theme, navigation, search, tabs
mkdocstringsDocumentation generated from Python docstrings
pymdown-extensionsAdditional Markdown extensions for Material
mkdocs-minify-pluginHTML/CSS/JS minification in site/
pip install mkdocs-material mkdocstrings[pymdownx]
Tip

Pin theme and plugin versions in requirements.txt. Material breaks compatibility between minor releases, as does mkdocstrings.

Creating a Project

mkdocs new scaffolds the project:

mkdocs new my-docs
cd my-docs

This creates a docs/ directory with index.md and an empty mkdocs.yml. That is the working minimum — nothing else is mandatory.

tree my-docs
my-docs
├── docs
│   └── index.md
└── mkdocs.yml

Directory Structure

docs/ is the single source of Markdown. The directory hierarchy maps directly to URLs. The file docs/guide/install.md becomes /guide/install/. An index.md in the root of docs/ is the landing page.

docs/
├── index.md
├── guide/
│   ├── install.md
│   └── config.md
├── reference/
│   └── cli.md
└── about.md

The site builds into the site/ directory next to mkdocs.yml. This directory is a build artifact — it is committed only for manual deploys, and usually built by CI.

Configuration mkdocs.yml

A minimal working config:

site_name: My Project Docs
site_url: https://example.com/docs/
docs_dir: docs
site_dir: site

theme:
  name: material

The full set of keys actually used in production:

KeyPurpose
site_nameSite title and default <title>
site_urlCanonical URL, required for sitemap.xml and robots.txt
site_descriptionDescription, goes into meta tags
docs_dirDirectory with Markdown, defaults to docs
site_dirWhere HTML is built, defaults to site
themeTheme and its parameters
navExplicit navigation, overrides auto-discovery
pluginsPlugins in load order
markdown_extensionsEnabled Markdown extensions
extraArbitrary variables read by the theme

Example with navigation, extensions, and plugins:

site_name: Service Docs
site_url: https://docs.example.com/
repo_url: https://github.com/example/service

theme:
  name: material
  features:
    - navigation.tabs
    - navigation.sections
    - search.highlight
    - content.code.copy
  palette:
    - scheme: default
      toggle:
        icon: material/brightness-7
        name: Dark theme
    - scheme: slate
      toggle:
        icon: material/brightness-4
        name: Light theme

nav:
  - Home: index.md
  - Guide:
      - Installation: guide/install.md
      - Configuration: guide/config.md
  - Reference:
      - CLI: reference/cli.md

markdown_extensions:
  - admonition
  - tables
  - toc:
      permalink: true
  - pymdownx.highlight:
      anchor_linenums: true
  - pymdownx.superfences
  - pymdownx.tabbed:
      alternate_style: true

plugins:
  - search
Warning

Enable search explicitly when using the plugins list. In newer Material versions it is no longer pulled in automatically from the theme.

Content

Markdown files are standard CommonMark with extensions. Useful constructs that work out of the box with the extensions enabled in the example above.

Admonitions:

> [!NOTE]
> A brief note for the reader.

> [!WARNING]
> This action may cause data loss.

Tabs with pymdownx.tabbed:

=== "Linux"

    ```bash
    sudo apt install foo
    ```

=== "macOS"

    ```bash
    brew install foo
    ```

Code highlighting with language specified:

```python
from mkdocs import config
print(config.DEFAULT_SCHEMA.keys())
```
Tip

Use heading anchors to link between pages. Material renders a # icon next to the heading when toc.permalink: true is set.

Internal links are relative paths from the current file:

See the [configuration section](config.md).

External links open in the same tab by default. To open in a new tab:

[Material for MkDocs](https://squidfunk.github.io/mkdocs-material/){target=_blank}

Build and Local Server

Local development — run the live server:

mkdocs serve

By default it listens on http://127.0.0.1:8000. Useful flags:

FlagEffect
--dev-addr 0.0.0.0:9000Change address and port, useful in a container
--strictBuild fails on any warning, including broken links
--livereloadPage reloads in the browser without F5 (on by default)
--no-livereloadDisable auto-refresh
--cleanRemove site/ before building

Build the artifact for deployment:

mkdocs build --clean --strict

--strict is mandatory in CI. Otherwise typos in links and missing files in nav will be silently built. The exit code on warning is zero, so without --strict the pipeline passes green with broken documentation.

Warning

Do not run mkdocs serve in production. It is a dev server with no authentication and file reload enabled.

Common errors on first run:

  • WARNING - A relative path to '...' is included in the 'nav' config. A file is listed in nav but missing from docs/. Check case and path.
  • WARNING - Documentation file 'x.md' is not included in the 'nav' configuration. The file exists but is not in navigation. Either add it to nav or rely on auto-navigation by removing the nav section entirely.
  • ERROR - Config value 'theme': The theme 'mkdocs' is not installed. The theme package is not installed, or the name is misspelled.

Material for MkDocs: Navigation, Search, Tabs

Material extends base MkDocs with three things that are almost always needed.

Navigation. Enabled through features in the theme section:

theme:
  name: material
  features:
    - navigation.tabs          # top-level tabs
    - navigation.sections      # tabs stick on scroll
    - navigation.top           # "back to top" button
    - navigation.indexes       # index.md becomes a section
    - navigation.tracking      # anchor in URL on navigation
    - toc.follow               # right-side TOC scrolls with text
Tip

The combination of navigation.tabs + navigation.sections gives the familiar “sticky” menu. Without sections the tabs scroll away with the content.

Search. The search plugin ships with Material but registers separately:

plugins:
  - search:
      separator: '[\s\-\.\_]+'

For Russian documentation, stemming is important — set search.lang: ru in the theme config. The list of supported languages is published in Material’s documentation and new languages are added regularly.

Tabs within a page. Implemented via the pymdownx.tabbed extension, connected above. The alternative syntax using !!! example blocks does not work in some themes — this is a frequent reason “why tabs don’t render.”

Code highlighting and copying. The “copy” button is enabled by the content.code.copy feature. Line numbers come from the pymdownx.highlight extension with linenums: true or globally via markdown_extensions:

markdown_extensions:
  - pymdownx.highlight:
      anchor_linenums: true
      line_spans: __span
      pygments_lang_class: true
  - pymdownx.inlinehilite
  - pymdownx.snippets
  - pymdownx.superfences
Warning

pymdownx.superfences is required for highlighting inside admonitions and tabbed blocks. Without it, code renders as a plain block without syntax highlighting.

Dark theme is a must-have for documentation read at night on-call. The toggle is configured in the example config above through palette with two schemes: default and slate. To make Material serve the correct scheme on first visit, add a custom script to <head> or use theme.palette.toggle with media — it works without JS and respects the user’s system settings.