Haskell project structure in NixOS

Posted in category nixos on 2017-10-15
Tags: haskell, nixos

Table of Contents

This blog post was inspired by the work of Gabriel Gonzalez at haskell-nix github repository which has a lot of practical information on how to structure a Haskell project in NixOS. Here I will show my own view on a typical Haskell project structure in NixOS taking into account Gabriel’s work.

I would encourage everyone interested to fork haskell-nix and go through Gabriel’s tutorial. That helped me understand some of the issues I was not aware before.


  1. default.nix must not change other than with $ cabal2nix . > default.nix
  2. shell.nix must be able to bring developer environment with Hoogle server that will serve documentation for all dependencies used in Cabal file
  3. release.nix must declare at least two packages - one with dynamically linked dependencies, and another - with statically linked dependencies
  4. release.nix must produce output expected by Hydra build system
  5. shell.nix as well as release.nix must use pinned nixpkgs to ensure reproducibility of builds
  6. shell.nix as well as release.nix must be open for choosing version of ghc compiler

Generate default.nix

This is mostly straightforward:

Pinning nixpkgs

To ensure reproducible builds it is important to fixate on a specific version of nixpkgs. Let’s now generate nixpkgs.json that will be used later in shell.nix and release.nix by issuing the following command:

This should produce the following output:

Building shell.nix

It is possible to generate shell.nix with $ cabal2nix --shell . > shell.nix, but it is not recommended as we are going to make sure we meet the requirement of running a Hoogle server locally with all the used dependencies.

Then in order to start your local Hoogle server:

Then navigate to in order to test if documentation server is running.

Building release.nix

release.nix also needs to use pinned version of nixpkgs. It also declares two haskell packages - one package-name and another package-name-static - dynamically and statically linked versions respectively. It also produces a set of derivations as a result which is what Hydra expects.

Make sure to rename package-name with your project name.

Then building the project locally ends up being as simple as:

And installing dynamically linked package with $ nix-env -i ./result. Or statically linked package with $ nix-env -i ./result-2.

Make sure you understand that package-name-static package is statically linked against current version of glibc used in the current system. This has some consequences that are not necessarily obvious. For example it will not necessarily work when packaged as alpine docker image. Reason being that alpine images are built using musl C-library instead of glibc, and since your package-name-static has glibc statically linked it won’t be possible to always properly run it. The worst part about it is that it will only fail in run-time, e.g. when resolving DNS for establishing a TCP connection. I am sure there will be some other issues too.