Technology / Software /
18 Jul 2022
Obsidian to Web with `mkdocs-material`
This seems like the easiest route to a nice-looking website, aside from just paying the Obsidian folks for their built-in sync and publish features (which I still might end up doing).
Setup
First, I created a Dockerfile
: (don’t copy this, it’s wrong; see the corrected one below)
Dockerfile
FROM squidfunk/mkdocs-material
ARG WITH_PLUGINS=true
ENV PYTHONDONTWRITEBYTECODE=1
COPY ./Technotes /docs
WORKDIR /docs
RUN mkdocs build
Then, I created a minimal mkdocs.yml
to configure the site itself:
YAML
site_name: Kadin's Technotes
site_url: http://kadin.sdf-us.org/technotes/
site_author: kadin@sdf.org
site_dir: site
theme:
name: material
And then I kicked off docker build .
and crossed my fingers.
Aaaaand… immediately I hit my first problem. The machine I was using was still i386, and it appears that mkdocs-material
is only distributed for x86_64. Whoops. Guess it’s really past time to upgrade the old home server. So I moved to my Linux laptop, which is 64-bit clean.
Second problem – a minor one, this time. After installing Docker, you need to do a couple of things: first, you need to make sure your user is in the correct group (sudo usermod -aG docker username
, after which you need to log out and then back in); second, you have to make sure the Docker daemon is actually running (duh), which is just a matter of sudo service docker start
. Now the real troubleshooting can begin!
Troubleshooting
My first semi-successful run of docker build .
ended with this error:
Config value: 'docs_dir'. Error: The path /docs/docs isn't an existing directory.
It would appear that the docs_dir
value isn’t being specified correctly. For some reason, it’s looking for /docs/docs inside the container, which doesn’t exist. Perhaps I don’t / shouldn’t change the WORKDIR into /docs in the Dockerfile? Let’s try commenting that out and seeing what happens…
Same error. Okay, so something more subtle is going on with docs_dir
, apparently.
Time to consult the docs. Here’s what they have to say about docs_dir
:
The directory containing the documentation source markdown files. This can either be a relative directory, in which case it is resolved relative to the directory containing your configuration file, or it can be an absolute directory path from the root of your local file system.
It would seem that docs_dir
is being resolved relative to the mkdocs.yml
file, which in my case is inside /Technotes
, causing it to not find the Markdown content. I think.
I decided to make the following adjustments to mkdocs.yml
:
YAML
# Input director for Markdown content
docs_dir: .
# Output directory for static HTML
site_dir: ../site
And that got us a much more understandable error message:
Config value: 'docs_dir'. Error: The 'docs_dir' should not be the parent directory of the config file. Use a child directory instead so that the 'docs_dir' is a sibling of the config file.
Apparently mkdocs
just really doesn’t like what I’m trying to do, with the YAML config file in the same directory as the content. Okay, fine, you win. I had hoped to have separate YAML files in each top-level content directory, so that I could build multiple sites from the same Obsidian vault, but maybe that’s not worth it.
So we just revert the dumb changes, git mv
that sucker up a directory, so it’s in the top-level Vault directory right next to the Dockerfile, and then do some adjustment to our Dockerfile so that we make sure we copy it into the container. Oh, and we also need to get rid of the WORKDIR /docs
line, because mkdocs
really, really wants to be run in the parent directory of both the input and output. Again, fine. Sometimes it’s best to let the Wookie win, or in this case, the software written by people who know more than me about this stuff.
And what do you know, it works!
Step 7/7 : RUN mkdocs build
---> Running in 3d4a44aa4d88
INFO - Cleaning site directory
INFO - Building documentation to directory: /site
INFO - Documentation built in 0.92 seconds
Removing intermediate container 3d4a44aa4d88
---> 52299ee963ab
Successfully built 52299ee963ab
Neato. Now, we just need to figure out how to get our artifacts (the generated static HTML) out of the Docker container and onto the host’s filesystem, so we can actually do something with it. And then we can start making the site look decent.
Except… it seems like Docker really isn’t designed to work this way? It looks like there is a way to get the files out, via a “multistage build” process, but that feels like I’m somehow misusing the tool. This doesn’t seem like it’s exactly an edge case or anything. (Goes back to Googling.)
It looks like the right way to do things is with a shared volume (see this tutorial). But for now, let’s just get the site up and running in a browser. This required a bit more tweaking to the Dockerfile, in order to replace the RUN
command with a proper ENTRYPOINT
so we could execute mkdocs -v serve
and not just mkdocs build
.
Here’s the command that actually got things going:
Bash
$ docker build -t mkdocs-test:v1 .
[...]
Successfully built a03707ebb56a
Successfully tagged mkdocs-test:v1
$ docker run -it -p 8000:8000 mkdocs-test:v1 -v serve --dev-addr=0.0.0.0:8000
[... lots and lots of debug output ...]
With this, I was able to go to localhost:8000 in Chrome and see my site. Hooray.
Cosmetic Setup
Okay, now it’s time to make this thing start looking decent. All the cosmetic stuff is controlled by mkdocs.yml
(which in my case is actually called technotes-mkdocs.yml
and then renamed as it’s copied into the Docker container).
Everything in mkdocs.yml
is pretty well documented, but there are some hard limitations that I’m a bit disappointed by. In particular, I’m not in love with the left-side navigation pane:
![[mkdocs-material-nav_2022-07-26.png]]
It seems like a slightly odd ordering, doesn’t it? That’s because it’s in alphanumeric order by file name. Not document title. The “Notes on Bluetooth trackers” doc is at the top, because its actual filename starts with “bluetooth”. Not ideal, for my purposes.
And MkDocs only has two ways of managing the nav tree: you can either let MkDocs build it automatically from the input files, or you can specify it manually in the YAML. But if you specify any part of it in the YAML configuration, that’s all you get – there’s no way to say “lay out these 5 files manually, then put the rest down here in filename order”.
Sample Configuration Files
Here’s stripped-down versions of my working configuration files.
Dockerfile
```Dockerfile FROM squidfunk/mkdocs-material ARG WITH_PLUGINS=true ENV PYTHONDONTWRITEBYTECODE=1
COPY ./Technotes /docs COPY ./technotes-mkdocs.yml /mkdocs.yml
WORKDIR /
RUN pip install mkdocs-roamlinks-plugin
ENTRYPOINT [“mkdocs”]
```
This throws some pip
warnings about running as root, but in general it seems to function fine.
MkDocs YAML (mkdocs.yml
)
```YAML site_name: Kadin’s Technotes site_url: http://kadin.sdf-us.org/technotes/ site_author: kadin@sdf.org site_description: >- Technical notes and projects. copyright: Copyright © 2022 Kadin (kadin@sdf.org)
site_dir: site
theme: name: material locale: en features: - navigation.instant # makes internal links faster - navigation.tracking # updates address bar to anchors palette: - scheme: slate - primary: blue grey - accent: cyan
markdown_extensions: - footnotes - pymdownx.arithmatex: generic: true - pymdownx.tasklist: custom_checkbox: true - def_list - pymdownx.critic - pymdownx.caret - pymdownx.keys - pymdownx.mark - pymdownx.tilde - toc: permalink: true
use_directory_urls: false
plugins: - roamlinks # for internal Obsidian links ``` Subject to the comments about flexibility above, this seems to work well.
References
- Running
mkdocs
with Docker - How to use MkDocs
- ‘obsidian-publish-mkdocs’ on Github’ - in retrospect, I should have just started here and saved several hours…
- MkDocs - Configure Pages and Navigation