Skip to content

kg4zow/mdbook-template

Repository files navigation

mdbook-template

John Simpson <[email protected]> 2022-05-30

Last updated 2025-08-03

This repo contains a template that I use when creating new "books" using mdbook, so I don't have to manually copy and edit a bunch of files. It contains an empty "book" generated by mdbook, with the following customizations:

  • Horizontal bars of varying thickness above H1, H2, and H3 headers. (See the generated output for an example.)

    I find this makes it easier to quickly spot the beginnings of the sections when the lines are used as separators between the sections. I also think it makes more sense to have the bars above the section headers instead of below them.

  • Automatic git commit and version information at the bottom of the navigation panel on the left.

  • A Makefile with rules to publish the HTML and other files that make up the book, either to a web server or to GitHub Pages.

https://kg4zow.github.io/mdbook-template/ contains the output generated from this repo, as published by "make gh-pages" in the Makefile.

TL;DR

Start a new book using this repo as a template

Clone the template repo.

$ cd ~/git/
$ git clone https://github.com/kg4zow/mdbook-template newbook
$ cd newbook/

⚠️ REMOVE THE .git/ DIRECTORY, so the directory on your workstation isn't "linked" to my git repository.

$ rm -rf .git/

Edit or remove the following files, as needed for your book:

$ nano book.toml
$ rm LICENSE.txt
$ nano README.md
$ sed -i -e '2,$d' src/introduction.md

If your text editor (or operating system) creates temp files, backup files, or other files which need to not be added to the repo, update the .gitignore file as needed.

$ nano .gitignore

If the generated HTML files are going to be hosted on a web server, or with a service like GitHub Pages or Keybase Sites, edit Makefile as needed. There are comments in the Makefile in the repo which may be useful, please let me know if you have any ideas to improve the file.

$ nano Makefile

Makefile

This file controls what happens when you run make. I'm going to assume you are already familiar with the basics of how Makefiles work, what targets are, and how to run make with a target name.

  • make build - runs mdbook once. It reads the markdown files under the src/ directory, and generates HTML, CSS, and other files under the book/ directory.

    Note that running "make" by itself will actually run "make build", because build is the first target in the file.

  • make serve - generates the book/ directory, but then it runs a small web server and opens a browser window so you can preview the generated site. It then watches the Markdown and other files which are the site's "source". When these files are changed on disk, it can signal the browser window to reload itself.

    This web server will only be accessible from browsers running on the same machine.

  • make serve-all - just like "make serve", but the web server will be accessible from other computers.

    I use this once in a while, to preview what a book will look like in a different browser or on a different device, or at work, to allow a co-worker to look at changes I'm proposing, before publishing them.

There are other targets in the file, which illustrate different ways you might want to "publish" the finished site. You will almost certainly need to update these targets (or add your own) in order to easily publish the book to whatever server(s) you want it published to.

I've included a few examples in the file.

  • make rsync - shows how to use the rsync command to copy the book/ directory to another machine, such as a web server.

  • make gh-pages - updates the stand-alone gh-pages branch, which will be created if you run the setup-gh-pages script. (This is covered below.)

  • make push - I've been using different systems to generate and publish content (web sites, ebooks, PDF files, etc.) for years, and using "make push" to publish content is a habit I've gotten into. In the Makefile in this repo it doesn't do anything, but in most of my other books I've updated it to look like one of these ...

    • push: rsync
    • push: gh-pages
    • push: external internal

    This way I can use "make push" no matter which book I'm working on.

You don't have to use the same targets I use. Feel free to write your own.

As another example, some of my books have a "make keybase" target which uses rsync to copy the book/ directory to a specific location in Keybase.

keybase: build
        rsync -avz --delete book/ /keybase/team/TEAM.NAME/SITE.NAME/

push: keybase

Just be careful when writing Makefile rules. The lines which contain recipes (the actual commands to be executed for each target) must start with TAB characters rather than spaces. This answer on Stack Overflow explains the details pretty well.

The git2rss script

The template's Makefile is set up to use my git2rss script to generate an RSS feed containing the commits in the repo's main branch.

If you plan to use this feature, edit the .git2rss file and customize the RSS feed's title, link, and other attributes as needed. When you publish the finished site, it will have a commits.xml file containing the feed. Whatever the URL is for the finished site, you can point an RSS reader to this file and "subscribe" to get notified whenever new commits are made to the book's source files.

For example, you can subscribe to https://kg4zow.github.io/mdbook-template/commits.xml to watch for updates to the kg4zow/mdbook-template repo.

Note that the file itself will contain an XML list of the repo's commit history. Assuming the repo is public, there's no information in the file that somebody couldn't get on their own by cloning a copy of the repo, or by visiting the repo's web interface (assuming it's hosted on a git server which has a web interface, of course).

If you don't want this feature, delete the git2rss script and the .git2rss file.

You can also remove the line in the Makefile which mentions git2rss. If you don't, the Makefile checks whether the script and config file both exist before trying to run the script, so it won't hurt anything if you don't remove the line.

Initialize a new git repo

Use "git init" to start a new repo (i.e. create a new .git/ directory with no commit history) on your computer, then create the first commit.

$ git init -b main
$ git add .
$ git commit

You may also want to create a tag, pointing to the first commit. This will allow the version info at the bottom of the page to include a count of how many commits were made after that tag. If you don't, the version info will contain only the commit hash.

$ git tag -m 'initial commit' initial

Hosting the Repo in Github

If you plan to host the repo in Github, the directions below will be useful.

If you plan to host the repo using some other service, the process will be similar to the directions below.

Github Pages

If you plan to publish the HTML version of your book using GitHub Pages, run the "setup-gh-pages" script. This will create a gh-pages branch with a totally separate commit history from the main branch.

⚠️ The script requires git version 2.0.7 or later.

$ bash setup-gh-pages

Once you've done this, or if you know you won't be using Github Pages, you can remove the script from your repo. This way you won't run it by accident. If you later decide you need it for some reason, you can always come back to this repo and download it again.

$ git rm setup-gh-pages
$ git commit -m 'Remove setup-gh-pages script'

Create the Repo

Create a totally empty repo in Github.

  • If you use the GitHub CLI tool, you can create an empty repo with one command.

    Obviously, use your own Github username and whatever repo name you like.

    $ gh repo create --public kg4zow/newbook
    âś“ Created repository kg4zow/newbook on GitHub
      https://github.com/kg4zow/newbook
    

    This example creates a publicly visible repo. This is required in order to use Github Pages (unless you have a paid Github account). If you're not going to use Github Pages and you want the repo to *not8 be publicly visible, you can use "--private" instead. (Keep in mind that Github employees are able to see what's in your repos, so don't store anything which needs to be kept secret.)

    The last line of the output will be the HTTPS URL for the repo. If you use SSH to access remote repos, you may need to convert it. In this example, the SSH URL would be "[email protected]:kg4zow/newbook".

  • If not ... use the Github web interface to create the repo.

    If you do this, be sure to NOT use any options that would automatically create files in the repo, such as README or LICENSE files. The repo you create must be empty.

    Once the repo is created, note the URL. You will need it below, in order to link the repo on your computer to the repo in Github.

Push the Repo to Github

Be sure to use the URL of the Github repo in the "git remote add" command.

$ cd ~/git/newbook/
$ git remote add origin [email protected]:kg4zow/newbook
$ git push --all -u
$ git push --tags

If you're publishing to GitHub Pages, you'll need to go into the repo's settings and enable Github Pages, published from the gh-pages branch. Github's documentation has more details on how to do this.

Be aware that it might take a few minutes for the web site to be accessible, and in the future it may also take a few minutes for the web site to be updated with new content.

Hosting the Repo in Keybase

Keybase offers several services, all fully encrypted, in a way that not even Keybase employees are able to read any of it. One of these services is encrypted git repos.

Each repo can be owned by a Keybase user (so it's only accessible by that user), or owned by a team (so it's accessible by members of that team).

Create the Repo

  • For a personal repo ...

    keybase git create REPONAME
    
  • For a team repo ...

    keybase git create --team TEAMNAME REPONAME
    

The command will create an empty repo on Keybase's servers, and print a message that looks something like this:

$ keybase git create REPONAME
Repo created! You can clone it with:
  git clone keybase://private/USERNAME/REPONAME
Or add it as a remote to an existing repo with:
  git remote add origin keybase://private/USERNAME/REPONAME

Push the Repo to Keybase

This is just like other services - add a "remote" pointing to the repo's URL, and push the commits and tags to it.

$ cd ~/git/newbook/
$ git remote add origin keybase://private/USERNAME/REPONAME
$ git push --all -u
$ git push --tags

Before you do anything else ... there is a bug in Keybase's git repo implementation where, when a repo is created, it assumes that the repo's primary branch will be called master. If you later use git clone to clone a copy of the repo, and the repo doesn't have a branch called master, that command will fail.

ℹ️ If you find yourself in this situation, you can use the git clone command's -b option to specify which branch to clone. For example ...

git clone -b main keybase://private/USERNAME/REPONAME

I normally use main as the primary branch for my repos, so my standard workflow when creating a git repo in Keybase is to manually edit the file on the "server" containing its idea of the primary branch name. This is possible because KBFS, Keybase's file sharing system, has a "hidden" directory which allows you to access the files on the git server.

$ cd /keybase/private/USERNAME/.kbfs_git/REPONAME/
$ cat HEAD
ref: refs/heads/master
$ echo 'ref: refs/heads/main' > HEAD
$ cat HEAD
ref: refs/heads/main

For a team repo, use /keybase/team/TEAMNAME/.kbfs_git/REPONAME/ instead.

Hosting the Repo in FOKS

FOKS, or Federated Open Key Service, is a new service by one of the original founders of Keybase. It has many of the same goals as Keybase, but is being written from the ground up using all-new code and stronger encryption algorithms. The source code for the client and the server are both available on Github, under the MIT License.

Currently (2025-08) the usable high-level features are encrypted key-value storage and encrypted git repos. I've been experimenting with FOKS for the past few weeks, and have started using it for encrypted git repos on a regular basis.

Like Keybase, FOKS supports the concept of "teams", with each user and each team having its own key-value store and git repos.

Create the Repo

  • For a personal repo, where you are the only user with access ...

    $ foks git create REPONAME
    Created: foks://SERVERNAME/USERNAME/REPONAME
    
  • For a team repo, where team members have access to the repo ...

    $ foks git create --team TEAMNAME REPONAME
    Created: foks://SERVERNAME/t:TEAMNAME/REPONAME
    

The command will output a message containing the repo's URL. You will need this below.

Push the Repo to FOKS

This is just like other services - add a "remote" pointing to the repo's URL, and push the commits and tags to it.

$ cd ~/git/newbook/
$ git remote add origin foks://SERVERNAME/USERNAME/REPONAME
$ git push --all -u
$ git push --tags

Working on Content

This section covers the typical workflow involved in writing or updating content in the book.

Start the development web server

In window 1 ...

$ cd ~/git/newbook/
$ make serve

This will generate the book's HTML, run a small web server, and open a browser window pointing to the rendered HTML.

For now, leave this window alone. Whenever you save changes to one of the files which make up the site, you will see log entries scroll by in this window when it re-generates the HTML.

Open your editor

In window 2 ...

$ cd ~/git/newbook/
$ bbedit .

For me, this opens BBEdit, my normal text editor. Obviously, feel free to use whatever text editor you like.

Edit the book

Do your thing.

As you save changes, the browser window that "make serve" opened should update itself with your changes. This lets you see the results of your changes right away (and for me, it lets me spot errors before I commit and publish them.)

Finished editing

When you reach a point where you're happy with your changes, commit them.

Commit and push changes

In window 2 ...

$ git add .
$ git commit

If the git repo is hosted with Github (or any other git host), push the changes. (This only pushes the commit to Github, it does not "publish" the site.

$ git push

Publish the book

Run "make" with the appropriate target name. As an example, using the push target ...

$ make push

Shut down the development web server

When you're totally finished, go back to "Window 1" where the make serve command is still running, and hit CTRL-C to stop it.

Background

This section will explain the customizations I'm making to how mdbook formats its output.

Section Lines

What are "Section Lines"?

If you're looking at the generated HTML from this template repo, you will notice horizontal lines of different thickness above some of the section headers. The three example section headers will have the three "sizes" of lines.

Note that these lines will only show up in the HTML that mdbook generates. If you're looking at this file in the GitHub web interface, you'll see GitHub's "Section Lines", which are different (i.e. only for H1 and H2, with H1/H2 lines being the same thickness, and with the lines below the section header text). GitHub does not allow any kind of custom stylesheets within their web pages (for security reasons) so there is no way to change this.

Why do I want this?

I do this because when I'm skimming through a long document, having the lines above the section header text makes it easier for me to see where each section starts. Also, having lines of different thickness makes it easier to tell major and minor sections apart.

How does this work?

This is done using two files:

  • section-lines.css - contains the CSS declarations which add the lines to the HTML.

    The CSS customizes the appearance of HTML H1, H2, and H3 elements, other than the first H1 on the page. I don't add the heavy bar above the first H1 because I use the same CSS when generating PDF files, I normally use an H1 header for the document title, and I didn't want to see that extra heavy bar across the top of the first page.

  • book.toml - tells mdbook how to generate the book. The following line tells mdbook, when it generates HTML output, to include the following CSS files in addition to the ones included in the theme.

    [output.html]
    additional-css  = [ "section-lines.css" , "version-commit.css" ]

    This example has a second file in the additional-css list. This other file is used by the Automatic Git Commit Information feature, which is explained below.

Automatic Git Commit Information

If you're looking at the generated HTML from this template repo, you will see a block at the bottom of the Table of Contents on the left, and a "footer" at the bottom of each main page, with information about the git commit containing the page's Markdown source.

  • The "Version" section contains information about the git commit from which the HTML was generated. This will include:

    • The most recent tag, if any. (This is part of why I always tag the first commit in a repo, especially an mdbook book.)
    • How many commits after that tag it is.
    • The commit hash itself (after the "g", which technically means git).
    • Maybe a "-dirty" indicator, which means that there were changes which hadn't been committed yet.

    Below this will be the timestamp of that commit.

  • The "Generated" section contains the timestamp when the HTML files were generated.

This is not something that mdbook does on its own, I had to figure out how to make it happen.

I am not the first person to figure out how to do this. I was able to get this working based on information from the links in this GitHub issue. My contributions, if any, are...

  • Bundling those commands into a Makefile, in a template repo that others are free to use.

  • Figuring out how and where to edit the "theme" files to put the version info at the bottom of the page and/or the Table of Contents.

  • Writing the version-commit filter script, in such a way that the """sh -c 'jq ".[1]"; sed ...'""" thing that I've seen in a few different places, isn't needed. (This particular command can be difficult for people to understand, especially if they're not used to working with scripting languages.)

  • Writing the documentation you're reading right now.

The way this repo does it it involves the following files:

version-commit

This is a shell script which does two things:

  1. Reads a stream of data provided by mdbook, and prints part of it out.

    When mdbook runs a filter, it sends that script a JSON list with two elements. The first element will contain information about the overall "book", and the second element will contain information about the "chapters", or pages, which make up the book. This includes the contents of the input Markdown files.

    Filtering scripts are expected to output a possibly modified copy of just the second element from the JSON structure. If the script happens to modify the JSON, it will change the "input" that mdbook processes, and thereby modify the generated HTML pages.

    In our case, we aren't modifying anything about the Markdown input itself. We're only writing a "filter" script because it's the only "hook" that mdbook provides to run user-supplied scripts before building the HTML pages.

    Because we aren't modifying the input, the script just prints the second element as-is.

  2. Reads the files in the "theme-template/" directory, substitutes a few values related to the state of the git working directory, and writes the modified files to the "theme/" directory.

    Specifically, the script substitutes values for the following tags:

    • @VERSION_COMMIT_HASH@ - the output from "git describe --always --tags --dirty", which includes the most recent tag and how many commits "ahead" of that tag we are, the commit hash (if it's different from the tag), and whether or not the content is "dirty" (i.e. if the working directory contains content which hasn't been committed yet).

    • @VERSION_COMMIT_TIME@ - the timestamp of that commit.

    • @VERSION_COMMIT_NOW@ - the timestamp when the script was executed by mdbook.

    All other content from the "theme-template/" files is copied as-is to files in the "theme/" directory.

The files in the "theme/" directory are used to override mdbook's built-in theme files when generating the HTML pages. Adding the commit information in the theme files is what makes them show up in the final HTML pages.

version-commit.css

Contains CSS to control the formatting of the items being added to the template.

In my case I wanted the text to be a bit smaller than the normal text in the Table of Contents, so the file I use contains some simple directives to set the text size and line spacing. I like the way it looks, obviously you're free to customize it any way you like.

theme-template/

These files are copies of files in the mdbook source code, with the appropriate lines added to make the version information appear where and how I wanted them.

The section about the version-commit script (above) explains this in more detail.

book.toml

This file is what tells mdbook how to create the HTML files for the book.

The [preprocessor.version-commit] section tells mdbook to run the version-commit script before generating the HTML output.

[preprocessor.version-commit]
renderers       = [ "html" ]
command         = "./version-commit"
  • If you want to change how or where the git commit information appears within the pages, edit theme/index-template.hbs. (See above for examples.)

  • If you want to change how the "version" or the timestamps are presented (i.e. to use a different "git describe" command to get the commit info, or to format the timestamps differently), edit the version-commit script.

  • If you don't want the commit information to be added at all, remove this section from the book.toml file.

This file also contains a declaration which makes mdbook include the version-commit.css file as part of the HTML files.

[output.html]
additional-css  = [ "section-lines.css" , "version-commit.css" ]

This example has a second file in the additional-css list. This other file is used by the Section Lines feature, which is explained above.

Updating theme-template/ files

There will be times when mdbook makes changes to the original files that this repo's theme-template/ files were copied from. In a few cases, these changes can "break" mdbook, especially since the mechanism used to make the browser reload itself automatically, is implemented in Javascript.

As an example, in 2024-11, mdbook v0.4.41 changed how the web pages are rendered. Previously, each page was in a single file and contained a copy of the ToC. Now, the ToC is a separate file, which two versions - one for browsers running javascript, and one for browsers not running javascript. I didn't notice this when it happened because it only comes up when running mdbook, not when viewing a page created by mdbook

The theme-template/ directory contains three .hbs files that were copied from mdbook's source code. If you're using a newer version of mdbook, you should update these files from mdbook's source code.

This is explained in more detail in the theme-template/ directory's README.md file.

Pre-requisites

The following items need to be installed on the machine where you're going to be working. Note that the instructions below are meant for macOS and Linux. I don't use ms-windows -- if you're stuck with it, you can always use VirtualBox and fire up a VM running Linux.

This is a quick list of what's required, I'm not going to go into a lot of detail about these.

mdbook

Obviously.

  • macOS using Homebrew: run "brew install mdbook".

  • Others: This page explains how to install mdbook.

git

Also obvious.

Note that the workflow described above, for publishing to Github Pages using a totally "disconnected" gh-pages branch, requires git version 2.0.7 or higher.

  • macOS: The git program is included as part of the XCode Command Line Tools. macOS comes with a git "stub" that will walk you though installing the XCode Command Line Tools if they aren't already installed.

    You can also install git using Homebrew if you like, however you will need to check your PATH to be sure that Homebrew's binaries are seen before the /usr/bin/ directory.

  • CentOS 7: run "yum install git" as root.

  • Debian 10 and 11: run "apt install git" as root.

  • Arch Linux: run "pacman -S git" as root.

  • Others: See the Installing Git page in the official git documentation.

jq

The version-commit script uses jq to parse the data stream it receives from mdbook.

  • macOS with Homebrew: brew install jq

  • Other: available through some distributions' package reopsitories, this page has details.

Note that if jq is installed in a non-standard directory, you may need to adjust the PATH= line near the top of the version-commit script to include the correct directory.

Perl (not any more)

The version-commit script was originally written in Perl. Several people reported problems with installing the JSON module, so I re-wrote it as a shell script that uses jq. If you have books which are currently using the Perl version, you can copy the new version-commit shell over it in your book's repo. As long as jq is installed, it should work the same.

License

Most of the content in this repo, including the version-commit script and the stylesheets, were written by myself and are licensed under the MIT License.

The files in this repo's /theme-template/ directory were copied from the /src/theme/ directory in the mdbook source and then modified. As such, these files are technically covered by the Mozilla Public License 2.0, as noted in their repo.

The files under /src/ were originally generated using mdbook, and in the case of /src/introduction.md, modified after that. I'll be honest, I'm not sure if this means they're covered under my MIT license or mdbook's MPL license, but either way, I have no intention of going after anybody who wants to copy and use them, and I seriously doubt that the mdbook developers will either.

Enjoy.

-jms1 2025-03-07

About

Template for 'mdbook' books with my preferences

Resources

License

Stars

Watchers

Forks