My Blogging Setup with WriteFreely


I couldn't find the perfect platform, but I might as well start somewhere.

What I was looking for was minimal, preferably easy to containerize and supporting postgresql.

I started out looking at Journey which had support for themes for Ghost, and was written in go. I was hoping to be able to build it without CGo so I could have a self contained static binary.

Development on it seemed frozen however, and although there were plenty of forks with patches applied, everyone seemed to have a different set of priorities. I tried creating my own fork and applying the patches I thought necessary before I realised I didn't want to start developing a blog before I start actually blogging.

Then I found WriteFreely which ticked 2 of the original requirements (minimal and easy to containerize), and once I've got it up and running I could look at adding support for postgresql later.

WriteFreely uses markdown for formatting which makes it easy to write out an article without too much distraction. It also comes with built in support for code highlighting.

It includes support for sqlite which requires CGo compilation however has compile options to disable this, hopefully allowing static builds. Adding support for postgresql shouldn't be too difficult if someone hasn't done it already by the time I get to looking into it.

Customisation is limited to a block of custom CSS which is included in public pages, which offers the benefit of not breaking the admin and settings pages if you manage to screw it up. There is a config option for a theme which looks like it just selects a css file from the static content included in the release archives. The limited customisation would at least keep me from getting distracted by it.

So with some playing around to get a feel for it I decided to go with it.



Although the WriteFreely devs do provide a docker image, their documentation doesn't recommend it for production use. I looked into their installation documentation and just wrapped the process up in Docker.

My first goal was to build a docker image based on the official releases. I wrote the Dockerfile to be able to pull the archive from git and wrap it up fully self contained.

Initially I wanted to base it on alpine to keep the image small, but the CGo dependencies were needed, so I ended up basing it on a minimal debian image. I also wasn't sure if the static content needed to be provided by a separate daemon or not.

Initially I looked at including nghttpd as a static http fileserver with supervisord launching both processes within the container based on recommendations in the docker documentation.

After I realised the writefreely binary could serve the static content itself I ripped that mess out but I kept it as a separate project in case I want to build on it later.

Ultimately I ended up with a 2 stage dockerfile. First extracting the package, then copying the contents into an image based on debian:buster-slim. Changing the release is as easy as modifying the version arg. I'll look into automating it as releases are provided by the devs, but for now it's manual.

Configuration and Launch

The image entry point assumes the configuration file is located at /opt/writefreely/data/config.ini with a working directory of /opt/writefreely/. I also keep the data and keys directory outside of the container for easy access on the host. I created a separate user to own those files and execute the container with that user.

Setup takes 3 steps:

  1. Initial configuration
  2. A couple of manual changes to the configuration
  3. Key generation

1. Initial Configuration

I create the config file using the built in configuration generator.

docker run --mount type=bind,source=/host/path/to/data,target=/opt/writefreely/data -it --rm --user `id -u writefreely`:`id -g writefreely` trainmeditations/writefreely /opt/writefreely/writefreely -c data/config.ini config start

I use the default port as it can be mapped to the host at runtime. I specify the path for the sqlite database in the data/ subdirectory. I also specify the correct public URL. This generates the config file and database.

2. Manual Changes

Primarily you'll need to change the bind address from localhost so that you can access it from the host or docker container running a reverse proxy. A lot of the configuration options generated are undocumented at this stage and I left those alone until I can investigate further.

3. Key Generation

Next step in the official documentation is generating keys:

docker run --mount type=bind,source=/host/path/to/data,target=/opt/writefreely/data --mount type=bind,source=/host/path/to/keys,target=/opt/writefreely/keys --rm --user `id -u writefreely`:`id -g writefreely` trainmeditations/writefreely /opt/writefreely/writefreely -c data/config.ini keys generate


Once this was all complete I launched the container, mapping the port and setting the restart so that the container is launched by docker at startup:

docker run -d --name writefreely -p --mount type=bind,source=/host/path/to/data,target=/opt/writefreely/data --mount type=bind,source=/host/path/to/keys,target=/opt/writefreely/keys --user `id -u writefreely`:`id -g writefreely` --restart unless-stopped trainmeditations/writefreely


At this point I've limited customisation to the custom CSS option. I've customised writefreely's layout as well as the syntax highlightjs theme for which I've used Atom One Dark by Daniel Gamage. A couple of hacks I found useful are over specifying elements to make my rules override existing rules, sometimes just prefixing them with body. This will cause warnings about overqualified rules which can be ignored. I'm sure if I was bothered I could specify them in a more “best practices” way but it works.

The custom CSS is included in the <head> of this page if you want to have a look.

In the future I would like to look into the theme file included and possibly modify it there. It should be possible to copy the static files out of the archive and customise them on the host, and mount them into the container, specifying the new path in the config.

Going forward

I have a few ideas about where I want to go with this going forward. I'd like to get postgresql support going. I really hope the devs document some of the configuration options better, but I might be able to do that when I'm digging around myself. Finally, I do wish the custom CSS was applied to the drafts page.

Although I currently have limited ability to commit myself to a project, I would like to contribute when I can.

If you've found this article helpful and would like to support me, would you consider buying me a coffee?