You created an awesome Python project, and you cannot wait to share it with the world — or just your colleagues. How can you make sure the users of your project know how it works (and allow you to respond RTFM to 90% of the issues)? You need a documentation site! This post will walk you through all the steps of my personal setup for creating and deploying documentation for a project.
Before we start
You’ve probably browsed documentation like the official Python docs, or the documentation of libraries such as FastAPI. These websites show the setup, features, best practices, references, release notes, and everything else there is to know about that particular project. These websites have at least one thing in common: they’re static.
A static website is made up of pre-built files stored on a server. When a user requests a specific page by entering a URL, the file linked to that URL is retrieved and displayed to the user within their browser. This is important to know and remember when we are going to deploy the docs site later in this post. Let’s get right into it! 👇
MkDocs
There are various libraries available to generate static sites for your documentation, of which sphinx, readthedocs and MkDocs are the most popular for Python projects.
My personal preference is MkDocs. To install it, run the following command inside your project root folder.
pip install mkdocs==1.6.1
pip freeze > requirements.txt
If MkDocs is successfully installed, it’s time to bootstrap the required code. Within your root folder, run:
mkdocs new .
This will create a mkdocs.yml file, and a docs folder. Now, by running mkdocs serve and navigating to http://127.0.0.1:8000/ in your browser, you can see your docs.
Material theme
MkDocs has gained popularity because of its Material extension. This extension comes with a lot of goodies. It allows you to write your docs in the familiar Markdown, has helpful plugins such as search, and supports 60+ languages, on all devices.
Install mkdocs-material by running:
pip install mkdocs-material==9.5.44
pip freeze > requirements.txt
Then, configure material as the MkDocs theme in the mkdocs.yml file created in the previous step.
site_name: Easy Docs
theme:
name: material
If you look at your docs locally, you can now see the difference. In my opinion, Material offers a much cleaner theme.

The before and after
Configuration
Now it’s getting exciting! The beauty of this setup, is that you have a lot of configuration options (you can even use Material as a blog!). I will walk you through the configurations and plugins that I normally use: Search, darkmode, navigation and helpful markdown features.
Search
One of the most useful plugins is search. Search is a built-in plugin that allows users to easily find the documentation they’re looking for by adding a better search box on the right side of the header. Enable the search plugin by adding the following section to your mkdocs.yml file:
plugins:
- search
Darkmode
We’re developers. We needdarkmode. With Material, we can easily add darkmode by modifying the theme section of the mkdocs.yml file:
theme:
name: material
palette:
- scheme: blue
toggle:
icon: material/lightbulb
name: Switch to dark mode
- scheme: slate
toggle:
icon: material/lightbulb-on
name: Switch to light mode
With this configuration, you allow users to toggle between themes. You can also configure the icons used for this, the config above includes my favourite combination.

Darkmode, to conserve our precious eyes 👀
Navigation
Last but not least, navigation. This is where you configure the organisation and hierarchy of your pages. There are a couple of ways to manage navigation, but I like to configure it in the mkdocs.yml under a dedicated nav section.
nav:
- Home: index.md
This simple navigation points the page called Home to the index.md Markdown file. It is good to know that the file paths are relative to the docs/ folder that was automatically created when you installed MkDocs.
You can create a new page by adding a markdown file to this list. This also works with hierarchy.
nav:
- Home: index.md
- Nested:
- Page: nested/index.md
- Multi:
- Level:
- Page: multi-nest/level/index.md
This will show up as:

Another way of organising your documentation, is through tabs. To enable tabs, add the following features to your theme section
theme:
features:
- navigation.tabs
- navigation.tabs.sticky
This feature will show the first level of your navigation as tabs in your header:

Helpful markdown features
The beauty of Material is that it builds on top of a familiar format: Markdown. These Markdown files are extended with a couple of helpful features, to make it easy to transfer knowledge to the reader of your documentation. In this section I want to highlight a few of these features.
Admonitions
The one feature I use most often are Admonitions. To enable admonitions, add a new markdown_extensions section to your mkdocs.yml:
markdown_extensions:
- admonition
- pymdownx.details
- pymdownx.superfences
Then, in a Markdown file, add the following:
!!! note
This is a note to show how to add-monitions
!!! tip
Admonitions are great to highlight important aspects
!!! warnings
Such as warnings
!!! bug
Or known bugs!
??? success
Admonitions are collapsable!
??? example "Example of a custom title"
You can combine features, and add custom titles as well
!!! question
Wondering what all the admonitions are? See: https://squidfunk.github.io/mkdocs-material/reference/admonitions/
This will show up as:

Annotations
Another very useful Markdown extension are annotations. To enable them, modify your extensions so it reflects the following:
markdown_extensions:
- admonition
- pymdownx.details
- pymdownx.superfences
- attr_list
- md_in_html
To add annotations, add the following Markdown:
Install the package, (1) and then run `mkdocs new .`.
{ .annotate }
1. To install the package, see the installation guide.
This adds a plus icon mid-sentence, and when a user hovers over that icon, it gives a popup with the additional information.

This feature helps you write code with high information density, whilst also accommodating less advanced users with guidance.
These are the two features I use most often, but Material has more. See here for the full list.
Publishing
If you’re ready with writing and configuring your docs, it’s time for the fun part, publishing! Deploying static sites can be done in a million ways, but I like to use Azure Static Web Apps (SWA). I have a couple of reasons (#nospon, unfortunately):
-
Azure SWA offers a generous free hosting plan, which should be enough to host small to medium size projects for free, indefinitely.
-
SWA allows you to protect your documentation, with an easy integration with the Azure AD. Very helpful for internal documentation.
-
There’s a very good GitHub integration.
-
Azure will bootstrap a large part of your CI/CD pipeline, which means you can be live within minutes.
To get started, open your Azure console, navigate to the Azure Static Web Apps service and click create. Choose your subscription and resource group, and give a descriptive name. In the next step, link the resource to your GitHub repository. When you’ve selected your repository, the Build Details will appear. Set the Build Preset to custom, and change the Output location to site. At the deployment configuration step, choose “Deployment Token”. Once you’ve selected your region you can deploy the app.
Azure will do two things: 1) Push an API Token into your GitHub secrets, 2) Create the GitHub workflow and run it. This pipeline will fail. We need to make a small adjustment. Add the following code block within the Checkout and Build & Deploy steps within your workflow (for the full pipeline code, see the file in the resulting repository):
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install -r requirements.txt
- run: mkdocs build
This code generates the documentation files by installing all requirements and running mkdocs build. The generated static files are then stored in the sites folder we set as output location earlier, making the content available for publishing. Visiting the URL will now result in viewing your docs!
For the resulting code, check the repository here.
Hi, I’m Bastiaan 👋🏼 I write about the Modern Data Workflow, where I explore tools & processes to supercharge your data capabilities. Follow me or datalyft for more posts like these!