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.
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.
Verification:
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:
| Package | Purpose |
|---|---|
mkdocs-material | Material theme, navigation, search, tabs |
mkdocstrings | Documentation generated from Python docstrings |
pymdown-extensions | Additional Markdown extensions for Material |
mkdocs-minify-plugin | HTML/CSS/JS minification in site/ |
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:
This creates a docs/ directory with index.md and an empty mkdocs.yml. That is the working minimum — nothing else is mandatory.
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.
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:
The full set of keys actually used in production:
| Key | Purpose |
|---|---|
site_name | Site title and default <title> |
site_url | Canonical URL, required for sitemap.xml and robots.txt |
site_description | Description, goes into meta tags |
docs_dir | Directory with Markdown, defaults to docs |
site_dir | Where HTML is built, defaults to site |
theme | Theme and its parameters |
nav | Explicit navigation, overrides auto-discovery |
plugins | Plugins in load order |
markdown_extensions | Enabled Markdown extensions |
extra | Arbitrary variables read by the theme |
Example with navigation, extensions, and plugins:
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:
Tabs with pymdownx.tabbed:
Code highlighting with language specified:
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:
External links open in the same tab by default. To open in a new tab:
Build and Local Server
Local development — run the live server:
By default it listens on http://127.0.0.1:8000. Useful flags:
| Flag | Effect |
|---|---|
--dev-addr 0.0.0.0:9000 | Change address and port, useful in a container |
--strict | Build fails on any warning, including broken links |
--livereload | Page reloads in the browser without F5 (on by default) |
--no-livereload | Disable auto-refresh |
--clean | Remove site/ before building |
Build the artifact for deployment:
--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.
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 innavbut missing fromdocs/. 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 tonavor rely on auto-navigation by removing thenavsection 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:
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:
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:
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.