---
author: "Robert Pearce"
authorTwitter: "@RobertWPearce"
desc: "Learn how to make your hakyll project build pure and how to patch hakyll if you need to."
keywords: "hakyll, nix, nixos, nixpkgs, niv, hakyll tutorial, hakyll blog, hakyll blog tutorial, hakyll static site, static site generator"
title: "Hakyll Pt. 6 – Pure Builds With Nix"
updated: "2023-01-30T23:00:00Z"
---

_Special thanks to [Utku Demir](https://github.com/utdemir/) for review and
constant inspiration._

This is part 6 of a multipart series where we will look at getting a website /
blog set up with [hakyll](https://jaspervdj.be/hakyll) and customized a fair
bit.

* [Pt. 1 – Setup & Initial Customization](/hakyll-pt-1-setup-initial-customization.html)
* [Pt. 2 – Generating a Sitemap XML File](/hakyll-pt-2-generating-a-sitemap-xml-file.html)
* [Pt. 3 – Generating RSS and Atom XML Feeds](/hakyll-pt-3-generating-rss-and-atom-xml-feeds.html)
* [Pt. 4 – Copying Static Files For Your Build](/hakyll-pt-4-copying-static-files-for-your-build.html)
* [Pt. 5 – Generating Custom Post Filenames From a Title Slug](/hakyll-pt-5-generating-custom-post-filenames-from-a-title-slug.html)
* Pt. 6 – Pure Builds With Nix
* [The hakyll-nix-template Tutorial](/the-hakyll-nix-template-tutorial.html)

## Overview

In this post we're going to create a new hakyll site from scratch with a caveat:
we will do just about everything with [nix](https://nixos.org) in order to
guarantee reproducibility for anyone (or anything) using our project. There are
also two bonuses that we will inherit simply because we are using nix:
* we will not need to rely on global package installs (apart from nix, of course)
* we will be able to easily patch any package problems; for example, if some of
  hakyll's dependencies are not available in nixpkgs, we can patch hakyll to get
  it to work.

Here is the example repository with what we're going to make:
https://github.com/rpearce/hakyll-nix-example

_Note: this post assumes that you have installed nix on your system._

## Steps to Build Our Hakyll Project With Nix

Make a new project with `release.nix`, `default.nix`, and `shell.nix`, and get
into its pure nix shell environment:

```text
λ mkdir hakyll-nix-example && cd $_
λ echo "{ }: let in { }" > release.nix
λ echo "(import ./release.nix { }).project" > default.nix
λ echo "(import ./release.nix { }).shell" > shell.nix
λ nix-shell --pure -p niv nix cacert
```

We won't have to touch `default.nix` nor `shell.nix` again, for we are
delegating their responsibilities to the `release.nix` file that we'll add
more to in a moment.

_Note: we require `nix` and `cacert` when running a pure `nix-shell` with `niv`
because of an issue (https://github.com/nmattia/niv/issues/222)._

Now that we're in the nix shell, initialize [`niv`](https://github.com/nmattia/niv)
and specify your nixpkgs owner, repository, and branch to be whatever you want:

```text
[nix-shell:~/projects/hakyll-nix-example]$ niv init
[nix-shell:~/projects/hakyll-nix-example]$ niv update nixpkgs -o NixOS -r nixpkgs-channels -b nixpkgs-unstable
[nix-shell:~/projects/hakyll-nix-example]$ exit
```

Update your `release.nix` file with the following:

```nix
let
  sources = import ./nix/sources.nix;
in
{ compiler ? "ghc883"
, pkgs ? import sources.nixpkgs { }
}:

let
  inherit (pkgs.lib.trivial) flip pipe;
  inherit (pkgs.haskell.lib) appendPatch appendConfigureFlags;

  haskellPackages = pkgs.haskell.packages.${compiler}.override {
    overrides = hpNew: hpOld: {
      hakyll =
        pipe
           hpOld.hakyll
           [ (flip appendPatch ./hakyll.patch)
             (flip appendConfigureFlags [ "-f" "watchServer" "-f" "previewServer" ])
           ];

      hakyll-nix-example = hpNew.callCabal2nix "hakyll-nix-example" ./. { };

      niv = import sources.niv { };
    };
  };

  project = haskellPackages.hakyll-nix-example;
in
{
  project = project;

  shell = haskellPackages.shellFor {
    packages = p: with p; [
      project
    ];
    buildInputs = with haskellPackages; [
      ghcid
      hlint       # or ormolu
      niv
      pkgs.cacert # needed for niv
      pkgs.nix    # needed for niv
    ];
    withHoogle = true;
  };
}
```

Don't worry, we'll circle back to what we just did.

Create a `hakyll.patch` diff file:

```text
λ touch hakyll.patch
```

Bootstrap the hakyll project (we won't ever need this again):

```text
λ nix-shell --pure -p haskellPackages.hakyll --run "hakyll-init ."
```

Build the project and `--show-trace` just in case something goes wrong:

```text
λ nix-build --show-trace
```

Run the local dev server:

```text
λ ./result/bin/site watch
```

Navigate to http://localhost:8000 and see your local dev site up and running!

## Understanding the `release.nix` File

Let's break down what we copied and pasted into `release.nix`.

```nix
let
  sources = import ./nix/sources.nix;
in
{ compiler ? "ghc883"
, pkgs ? import sources.nixpkgs { }
}:

# ...
```

The `let` gives us the space to define an attribute (variable), and it is here
that we import our `sources.nix` file that was generated by `niv`. The `in`
block defines a function parameter with two attributes, `compiler` and `sources`,
that each have defaults (when there's a `:`, that means what comes next is a
function body or another function argument). For the `compiler`, we will use
this version to compile all of the Haskell packages that we interact with. For
the `pkgs`, we default to using our pinned version of `nixpkgs`, but this is
overridable.

```nix
# ...

let
  inherit (pkgs.lib.trivial) flip pipe;
  inherit (pkgs.haskell.lib) appendPatch appendConfigureFlags;

  # ...
```

Our new `let` falls within the function we created above, and we then state that
we would like to inherit some nice functions from [`pkgs.lib.trivial`](https://github.com/NixOS/nixpkgs/blob/master/lib/trivial.nix)
and [`pkgs.haskell.lib`](https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/lib.nix).
The `flip` and `pipe` functions are standards in functional programming, but
I'll share a short recap:
* [`flip`](https://github.com/NixOS/nixpkgs/blob/3a1e8bdcd9a9e13ccd3f298bf598acc8b35caa4d/lib/trivial.nix#L130-L138)
  takes a function `a -> b -> c` and flips the accepted arguments to act like
  `b -> a -> c`. Its definition is `flip = f: a: b: f b a;` – it takes a
  function, then `a`, then `b`, and then it applies `a` and `b` in reversed
  (flipped) order.
* [`pipe`](https://github.com/NixOS/nixpkgs/blob/3a1e8bdcd9a9e13ccd3f298bf598acc8b35caa4d/lib/trivial.nix#L32-L63)
  establishes a set of functions that you can apply data to, one after the
  other. Think of bash pipes: `cat blog_post.txt | grep nix`.

```nix
let
  # ...

  haskellPackages = pkgs.haskell.packages.${compiler}.override {
    overrides = hpNew: hpOld: {
      hakyll =
        pipe
           hpOld.hakyll
           [ (flip appendPatch ./hakyll.patch)
             (flip appendConfigureFlags [ "-f" "watchServer" "-f" "previewServer" ])
           ];

      hakyll-nix-example = hpNew.callCabal2nix "hakyll-nix-example" ./. { };

      niv = import sources.niv { };
    };
  };

  # ...
```

This uses our pinned (or overridden) `nixpkgs` to create our own
`haskellPackages` for a specific Haskell `compiler` version.

For `hakyll`, we need to make sure it gets compiled with the `watchServer` and
`previewServer` flags, or we won't be able to use its local dev server. We also
provide an optional patch file (`git diff > hakyll.patch` file) that we can
build `hakyll` with if there are any changes to the project that we need to make.
Patch files can be empty when no patches are required, but if you do need to
patch something, here is an example `hakyll.patch` file:

```diff
diff --git a/hakyll.cabal b/hakyll.cabal
index fcded8d..9746f20 100644
--- a/hakyll.cabal
+++ b/hakyll.cabal
@@ -199,7 +199,7 @@ Library
   If flag(previewServer)
     Build-depends:
       wai             >= 3.2   && < 3.3,
-      warp            >= 3.2   && < 3.3,
+      warp,
       wai-app-static  >= 3.1   && < 3.2,
       http-types      >= 0.9   && < 0.13,
       fsnotify        >= 0.2   && < 0.4
```

The `hakyll-nix-example` attribute is specifically for our Haskell project in
order for us to be sure our project is compiled with our desired compiler
version. We leverage the `callCabal2nix` tool to handle automatically converting
our `hakyll-nix-example.cabal` file into a `nix` derivation for our build.

Lastly, we ensure that the `niv` we are using in the `nix-shell` is our pinned
`niv` that `niv` itself generated.

```nix
let
  # ...

  project = haskellPackages.hakyll-nix-example;
in
{
  project = project;

  # ...
```

The `project` attribute is what our `default.nix` will use when being called
with tools like `nix-build`. All we do is access our `hakyll-nix-example`
attribute from our customized `haskellPackages`.

```nix
let
  # ...
in {
  # ...

  shell = haskellPackages.shellFor {
    packages = p: with p; [
      project
    ];
    buildInputs = with haskellPackages; [
      ghcid
      hlint       # or ormolu
      niv
      pkgs.cacert # needed for niv
      pkgs.nix    # needed for niv
    ];
    withHoogle = true;
  };
}
```

Exactly like `default.nix` uses the `project` attribute, `shell.nix` is looking
for a `shell` attribute to define everything it needs when running `nix-shell
--pure`. We use `shellFor`, which comes with the `nixpkgs` Haskell tools, and we
provide it a few attributes:
* the `packages` attribute holds our `project` package and any other `nixpkgs`
  that you would like to have built when entering the shell
* the `buildInputs` attribute holds all the tools that we'll have available to
  us while we're in the shell; for example, you can run `ghcid` and load your
  Haskell code to test it out or run `hlint` to lint your Haskell files
* `withHoogle` gives us the ability to query https://hoogle.haskell.org

## Wrapping Up

Using nix to build our project helps make development consistent and
predictable; however, learning nix is not necessarily a breeze. The following
articles directly contributed to my understanding that led to this post:

* https://nixos.org/nixos/nix-pills/index.html
* https://github.com/Gabriel439/haskell-nix/tree/master/project4
* https://turbomack.github.io/posts/2020-02-17-cabal-flags-and-nix.html (this
  article is what led me to learn how to patch hakyll)
* `overrideAttrs`: https://nixos.org/nixpkgs/manual/#sec-pkg-overrideAttrs
* `overlays`: https://nixos.org/nixpkgs/manual/#chap-overlays
* https://maybevoid.com/posts/2019-01-27-getting-started-haskell-nix.html
* https://kuznero.com/post/linux/haskell-project-structure-in-nixos/
* https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/haskell.section.md
* https://nixos.org/nixpkgs/manual/#haskell

* * *

Thank you for reading!
<br />
Robert
