Skip to content

Hello World

It seems that every 10 years I feel compelled to renovate the way I blog. I've just installed new blog system on my Mac. It uses Mkdocs, or more precisely its variant Material for MkDocs1, an open source SSG and is published on Gitlab Pages. I have used it as the documentation system in my previous organisation and I liked it lot, and I recently realised it is good for blogging systems too.

First step: install the blog system

brew install pipx
pipx install --include-deps "mkdocs-material[imaging]

Warning

Note that pipx creates a venv at ~/.local/pipx/venvs/mkdocs-material

Install additional plugins

mkdocs-material comes with a lot of plugin already, but you may want to install others. In which case you need to install the python package and then enable it in the Mkdocs configuration file.

source ~/.local/pipx/venvs/mkdocs-material/bin/activate
python -m pip install mkdocs-rss-plugin

and in mkdocs.yml, add:

plugins:
  - rss

Failure

If you don't activate the venv for the mkdocs-material package or activate a different one, mkdocs build process will complain that the plugin is not installed

Tip

It's probably a good idea to freeze all of the python dependencies into a requirements.txt file to make it easier to install and upgrade: python -m pip freeze > requirements.txt

Create a new project

mkdocs new .

This will create a docs directory and a index.md file inside it, and a mkdocs.yml alongside it. The index file double as rudimentary "Getting started" doc, so it's worth opening and reading it.

Configuration

All the configuration for mkdocs goes into the file mkdocs.yml at the root of the project. It has several sections, to configure the website's metadata, and the plugins2 that are going to be used. A typical configuraiton for blog only project looks like this:

site_name: rmy blog
site_description: Musings on things
site_dir: public
site_url: https://blog.example.com
plugins:
  - blog:
      blog_dir: .
  - social
  - tags
  - search
  - meta
  - rss
theme:
  name: material
  features:
    - navigation.indexes

Note

in the plugins section, all listed but the last one are included in the mkdocs-material python package, so they don't need separate installation. However they may have prerequisite, hence the [imaging] passed to the forementioned package in the first section.

Warning

Of all the plugins, blog is the only one required for a blogging system. Mkdocs is primarily a technical documentation system, so blogging is not core functionality.

Tip

like what we did for the first plugin blog, all the plugins can take additional parameters. You can see the default plugins list and their configuration on the Material for Mkdocs documentation.

Build the public site

mkdocs build

This process will create an output directory (by default site, unless it has been overriden by site_dir configuration directive) in which the directory structure in docs is recreated and all the Markdown files and their metadata are proccessed and rendered as a static html website.

Warning

if the configuration file, mkdocs.yml doesn't exist or is missing directives, there will be error shown in the log and the site won't be created.

Serve the website locally

mkdocs serve

Note

mkdocs serve monitors file system change and will reload the local site whenever a file has been modified. Errors will be shown in the log.

Success

if the site building succeed, the local website (for preview only, it's not production grade) is availble at http://127.0.0.1:8000/

RSS feed

The RSS feeds will be created in multiple format (XML Atom and Json) and there will be separate feed for created entries and updated entries.

Example

curl -s http://localhost:8000/feed_rss_created.xml | xmllint --format -

Metadata: Categories, tags and draft

Like most Markdown-based SSG that I know of, Mkdocs supports embedding metadata about an article in the Markdown document itself, at the top and enclosed beweenm two lines made of ---. That header section is call "Front matter", and that's where you can define the title, creation date, as well as listing the authors involved and the tags associated with it. That's where one can indicate whether a document is a draft. Of them all, only title: and date: are mandatory.

Example

---
title: my amazing post
date: 2032-12-12
categories:
  - Long post
  - Travel
tags:
  - food
  - vacation
  - weekend
draft: true
authors:
  - janedoe
---
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa

Authors

In the example above, you may be wondering where does janedoe came from. Authors are defined in a docs/.authors.yml file and it look like this:

authors:
  janedoe:
    name: Jane Doe        # Author name
    description: Conservationist  # Author description
    avatar: https://test.com/jane.jpg         # Author avatar
    # slug: url           # Author profile slug
    # url: url            # Author website URL

The properties that are commented out are the optional ones.

Meta plugin and project structure

One of the builtin plugin is called meta and it allows defining a file (by default .meta.yml) that you put inside a directory. Then, any markdown you drop inside that directory will inehrit the metadata held in the meta file (and merged with the document's own metadata), saving you from the hassle of adding repeatedly the same front matter directives in multiple Markdown files. That way you can for example create a directory for each category and the .meta.yml file in each will have the front matter directive to describe that category, so you don't have to do it in the Markdown document that you just drop in the appropriate category directory.

You can use the same technique to create a Draft directory with a .meta.yml file containing only draft: true. Any draft you want to create goes into that directory. When a document is ready to publish you move it out of the Draft directory.

Here's how it looks for me in my initial setup:

── docs/
│   ├── .authors.yml
│   ├── index.md
│   ├── posts/
│   │   ├── CheatSheets/
│   │   │   └── .meta.yml
│   │   ├── DeliberatePractices/
│   │   │   └── .meta.yml
│   │   ├── Drafts/
│   │   │   ├── .meta.yml
│   │   │   ├── git-workflow.md
│   │   │   └── post-as-draft.md
│   │   ├── InterestingLinks/
│   │   │   └── .meta.yml
│   │   ├── LongArticles/
│   │   │   └── .meta.yml
│   │   ├── Ramblings/
│   │   │   └── .meta.yml
│   │   └── Tutorials/
│   │       ├── .meta.yml
│   │       └── first-post.md
│   └── tags.md
├── mkdocs.yml
├── public

Note

mkdocs's blog plugin expects the existence of docs/posts diretory, and any document inside it (directly or inside sub-directories) will be managed by it (will have it slug, archive generated, and front matter metadata processed, etc).

Directory and document created outside posts will stil be processed by mkdocs as a regular documentation document, not as blog posts. Ideal for ancilliary pages like Contact, About, etc.

Deploy the website

This is the part that's different for everyone. Mkdocs blog's static website output is ideally suited for the static page hosting offering from major managed git providers, especially that mkdocs was built as a technical documentation system. In particuliar if you use Github or Gitlab, the process is easy. For Gitlab, you create a project and then follow the wizard in "Deploy > Page" from the sidebar.

For reference, here's how my .gitlab-ci.yml looks like:

# The Docker image that will be used to build your app
image: python:latest
create-pages:
  stage: deploy
  pages:
    # The folder that contains the files to be exposed at the Page URL
    publish: public
  rules:
    # This ensures that only pushes to the default branch will trigger
    # a pages deploy
    - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
  # Functions that should be executed before the build script is run
  before_script:
    - pip install "mkdocs-material[imaging]"
    - pip install mkdocs-rss-plugin
  script:
    - mkdocs build
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - ~/.cache/ 
  artifacts:
    paths:
      - public