Making docs with MkDocs
Posted on Thu 23 February 2023 in blog • 7 min read
This week, work launched a new documentation web site (Cleura Docs) which my team and I had been building for several months. Since this was my first foray into any significant tech writing in about 7 years, it was a fun exercise to see what tools are now available to the community, and how the technical landscape has changed in the interim.
This post is a summary of the technical considerations that went into creating that site, and the functional decisions that we made building it.1
What we use
We had, early on, made the decision that the site would use Markdown as its primary documentation format. This is because Markdown strikes a nice balance between richness of expression, and ease of use.
reST and DocBook are probably much more appealing to the die-hard tech writer, but they are also somewhat impenetrable and difficult to grok. AsciiDoc is just as expressive as DocBook (and indeed is semantically equivalent to it), but it is also somewhat obscure and niche. Markdown, in contrast, is ubiquitous and comparatively intuitive, which makes it accessible to contributors who aren’t full-time professional writers, which is exactly what we were looking for.2
What we also wanted was a static site generator that could be kicked off from a CI run, with the ability to host the results pretty much anywhere. We currently run on GitHub Pages, but there is nothing that keeps us from running anywhere else.
So the combination of those factors quickly led to the selection of MkDocs, which I had last used in 2016 or thereabouts, and holy mackerel has it come a long way since. This is in no small part due to Material for MkDocs (also known as mkdocs-material), which is a staggeringly excellent way to render MkDocs sources, and has become something of a contemporary de-facto standard for technical documentation in the industry.
Finally, as a theoretical documentation framework we adopted Diátaxis, which is also becoming something of an industry default.
We furthermore decided that within the Diátaxis framework, we would follow this order of priorities: How-to guides would come first, followed by the necessary amount of reference material. Once those bits were considered sufficient to be useful (not “complete” — documentation is never complete), we would be ready to drop the “beta” warnings from the site and announce it publicly. Then we would start adding background explanations, and finally, tutorials.
As I write this article, the site is out of beta, we have just started on the background bits, and no tutorials do as yet exist — although we do have academy.cleura.cloud which has full-blown training courses.
How we use it
A non-technical decision that we also made early on is that the documentation should be available under a Creative Commons license, and that its whole build chain should be publicly available. This is nice, because it allows me to go into the nitty-gritty of some technical details.3
So, I am next going to go into a few elements of our MkDocs configuration that we found particularly useful.
Plugins
There is an inordinate number of plugins available for MkDocs (and some, specifically, for mkdocs-material), of which we use a handful.
macros
This is a plugin that allows you to use Jinja2 expressions in your Markdown sources.
It’s exceedingly useful because product and service names, and other terms that may be relevant to your technical documentation, change.
Whenever that happens, you normally hear tech writers groan because they now need to dust off their grep
and sed
skills and embark on a massive search-and-replace effort.
With mkdocs-macros
, you just define a variable under the extra
key of your mkdocs.yml
dictionary, and you’re off. Like so:
extra:
support: "Service Desk"
plugins:
- macros:
# These settings are helpful because you want your build to fail if you're using an undefined macro.
on_error_fail: true
on_undefined: "strict"
And then you can do this, in your Markdown sources:
## Getting Help
For any further questions, contact our {{support}}.
Then when your support team decides they want to rename from “Service Desk” to “Service Center”, you change just one line in your configuration.
git-authors
I think it’s always valuable to credit people’s contributions individually.
The git-authors
plugin lets you do that quite nicely.
And it even gave me the opportunity to make a tiny code contribution in the process of incorporating it into our build.
plugins:
- git-authors:
enabled: true
show_email_address: false
You can take a look at any random page on the site for the inconspicuous “Authors” list at the bottom of the page.
htmlproofer
One of the things that everyone hates when perusing documentation is when it contains dead links.
I think it is therefore incumbent on us documentation authors to employ a link checker, run it on every build, and not publish documentation that links to HTTP 404s.
The htmlproofer
plugin lets us do just that:
plugins:
- htmlproofer:
# We want dead links to fail the build, not just produce a warning.
raise_error: true
validate_external_urls: true
Note that this can add a significant amount of time to the build (up to 50 seconds, in our case), so we find it helpful to be able to disable external link checking when we run mkdocs serve
locally.
We can do that by adding one more line to the configuration:
plugins:
- htmlproofer:
enabled: !ENV [DOCS_ENABLE_HTMLPROOFER, True]
raise_error: true
validate_external_urls: true
Now if we don’t do anything specific, links will be checked. This is also what we use in CI runs.
However, we can also do this, which greatly facilitates work-in-progress:
export DOCS_ENABLE_HTMLPROOFER=false
mkdocs serve
Content tabs
A feature in mkdocs-material that has proven to be very useful are content tabs.
It turns out that more often than not, particularly when dealing with an infrastructure platform, there’s more than one way to do something. Then, you often end up interspersing explanatory content (which is the same regardless of the tool you use) with command examples (which are of course tool-specific). The use of content tabs makes this kind of content a breeze to write and maintain.
For example, we expose an S3-compatible object store API with Ceph radosgw, and there you can frequently do things just as well with aws s3api
or s3cmd
or mc
.
With content tabs, we are able to explain complex features in a relatively uncluttered and compact way, without losing the necessary detail.
This comes in handy even if we want to be specific about some functionality not being available in a particular tool. Consider this example from the page on S3 bucket versioning:
## Enabling bucket versioning
To enable versioning in a bucket, use one of the following commands:
=== "aws"
```bash
aws --profile <region> \
s3api put-bucket-versioning \
--versioning-configuration Status=Enabled \
--bucket <bucket-name>
```
=== "mc"
```bash
mc version enable <region>/<bucket-name>
```
=== "s3cmd"
This functionality is not available with the `s3cmd` command.
Git integration
Material for MkDocs has excellent integration with GitHub, GitLab, and other Git-based revision control and collaboration platforms.
We chose to use that to its fullest extent, to the point where every single page has an edit button, and things are made as easy as possible for drive-by contributors. We also wrote a guide for submitting changes, and a general contribution guide.
For people who don’t want to write a patch but do want to report a problem or bug, we implemented GitHub issue forms (currently in beta, we hope they stay) — and wrote a separate guide for using those, too.
CI and deployment automation
Our test/build/deploy pipeline runs from tox
, very similar to what I’ve covered in some detail before.
This means that we can ship a .githooks
directory enabling documentation contributors to run the full test suite on every commit and push, that we can keep our GitHub Actions workflows rather simple and lean, and that we can switch to a different build platform (such as GitLab) quite easily if we choose.
Analytics
At work we are acutely GDPR conscious, so Google Analytics were a non-starter. Thankfully, there is a European, privacy-preserving, lightweight site analytics solution in Plausible (which I also use for my site), which you can incorporate into mkdocs-material with a very tiny theme override. Feel free to take a look at the PR if you want to do something similar.
How it’s going
Overall, feedback on the new site has been unanimously positive. This is nice, but what is even better (and highly important, in the long run) is that people evidently find it very straightforward to make contributions. Our colleagues no longer even ask how they can help out — they just do it, some of them making extremely impressive content additions even on their first PR.
So that feels very promising.
-
There is also an article the Cleura blog, published a few weeks after this post, that talks about the organizational considerations that went into building the documentation platform. ↩
-
Lest you think I am bashing something I am clueless about, I have used all the mentioned formats for technical documentation in a professional capacity: reST (with Sphinx) for contributions to the Ceph and OpenStack docs, AsciiDoc in the context of Linux-HA, and DocBook XML for ancient versions of the Pacemaker documentation and, believe it or not, for my thesis. And Markdown, obviously, for too many things to count. ↩
-
You are welcome to take a peek at the GitHub repo where we maintain the documentation sources and CI infrastructure. ↩