Deploying Hakyll Blog with Nix and Github Actions

Posted on January 10, 2021

# Motivation

My blog was originally running on hugo with nginx on a digitalocean server. But, it’s a static site! I shouldn’t need to pay to host it on a server when I could just be deploying to github pages for free.

I recently tried to pick up haskell, and wanted to configure my blog in haskell. This can be done through hakyll. I get the same benefits that hugo provides (e.g. static site generation from markdown files, except with pandoc) ; the configuration part is a bit easier and more powerful since it’s in haskell instead of a yaml file.

My vision was to have hakyll be able to convert my markdown, css/html templates, and haskell config file into a static website. I also didn’t want to do this every time I had a modification. The workflow I envisioned went like:

  • push modified markdown, css/html template, haskell config to github
  • github would grab nix on a build server and use nix flakes to convert to a static website.
  • that static website would be pushed to a separate branch of the repo that is deployed to github pages and link with my CNAME (

# Building as a flake

The first thing to do was figure out how to generate the blog using nixFlake. Luckily, this guy had already done this in a readable manner. However, he was hosting on a nginx instance, which I didn’t want, so I cut some of what he did. The barebones is just in my flake.nix file.

This flake builds hakyll, then the static site (builder) then copies the generated site (plus CNAME) to $out. I was then able to get nix to deploy using the process described here and here. Note that I didn’t realize this template existed until after I had put together this tooling. The template’s nix tooling is quite nice.

The CI steps are:

  • Use the checkout action to clone the repo
  • Use the cachix/install-nix-action to install nix flakes.
  • Run the nix build . -o site to build the flake and put the result in the site subdirectory.
  • Use crazy-max deploy to github pages action. This requires a GITHUB_TOKEN which is already autogenerated by github. It looks at the build_dir field to figure out what to deploy, then pushes that folder to the target_branch field (gh-pages). This is then read in by github pages and deployed to the website in site/CNAME (which was generated with the flake.nix).

# Configuration

Generating the blog content is done by first nix develop-ing, then running hakyll-init in the root directory; this generates the template to modify. Then building is as easy as:

nix build .

The website workflow is easy to control from the Main.hs file, and the template’s css and html is easy enough to modify as well.

As with all static website generators, live refresh upon modification of css is a must for rapid prototyping. Hakyll allows this via:

nix develop
site --watch

# Google Analytics

I’m also interested in data, and one way to get data is to hook the site into google analytics. Google gives your site a script to put in the HEAD of each page, and it will provide you with analytics based on the requests to the page. I’ve done this and it’s pretty nifty (though I’m the only user). Hakyll makes this easy, since all I need do is modify the template used to include the analytics script and boom it’s on all webpages.

# Adding style

To make the site look a little less outdated, I’m using nerdfonts. This was as easy as including a line in the HEAD section and a little bit of css per nerdfont README.

# Adding the Resume

So, my resume is built with latex. I would open source (and perhaps will) the repo I’m using for it. However, there’s personally identifying information like my mail address and phone number in the git history, and I’m too lazy to scrub that.

In any case: my resume is in latex and built with xelatex and latexmk. I recently turned it into a flake by modifying the default template to look like:

  description = "Justin Restivo Resume";

  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-20.09";

  outputs = { self, nixpkgs }: {

    defaultPackage.x86_64-linux =
      with import nixpkgs { system = "x86_64-linux"; };
      stdenv.mkDerivation {
        name = "justinrestivo_resume";
        src = self;
        buildPhase = "latexmk -pdf";
        buildInputs = with nixpkgs;
            (texlive.combine {
              inherit (texlive) scheme-medium lipsum fmtcount datetime;
        installPhase = "mkdir -p $out/; mv *.pdf $out/";

This is a very simple flake that basically defines the latex dependencies and latexmk build command.

Now, if I want to build this into my website flake, I can list the resume repo as an input to the flake:

  inputs.justinrestivo_resume = {
    url = "git+ssh://";
    flake = true;

Then, in the build phase of justinrestivo-me, I can just copy the pdf output of my blog’s flake into the output site directory and have it be accessible (and linked to by the blog):

cp ${justinrestivo_resume}/justin_restivo_resume.pdf $out

In theory this is great, but I still needed it to play nicely with CI. This all built fine locally, but did not with github actions. It couldn’t grab the flake since github CI didn’t have permissions to clone the private repo that contained my resume flake. To get around this, I had to add in another github action that started ssh-agent with a custom private ssh key. The relevant piece of the yaml is:

         - uses: webfactory/ssh-agent@v0.4.1
              ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

So first I had to generate a private ssh key, add it to my githubs, and then as a repo secret with the label secrets.SSH_PRIVATE_KEY.

Still, after I had ssh-agent start like that, everything worked smoothly. Now, nix builds my resume as a dependency, then builds my website with the resulting resume pdf included.