How to create a custom nixpkg for a Haskell application

December 17, 2017

Package a custom Haskell application as a nix package to simplify and automate both deploys and rollbacks.

Every nix package is immutable and identified (at least in part) by a hash:

The inputs used to compute the hash are all attributes of the derivation … These typically include the sources, the build commands, the compilers used by the build, library dependencies, and so on. This is recursive: for instance, the sources of the compiler also affect the hash.

This makes deploys and rollbacks safe to automate. Instead of a deploy that overwrites the “global variable” /usr/local/bin/eventarelli-api with a new executable, nix overwrites it with a symlink that points to the newly-built and immutable executable in /nix/store. A rollback points the symlink to the previous version.


Software used for this HOWTO:

  1. nix-build (Nix) 1.11.15
  2. nix channel

Step 1: Install nixpkgs

  1. Follow steps at
  2. Add 17.09 channel to your user environment:


$ nix-channel add


$ nix-channel add

Step 2: Install cabal2nix

$ nix-env -i cabal2nix
installing ‘cabal2nix-2.6’
these paths will be fetched (0.00 MiB download, 0.00 MiB unpacked):
fetching path ‘/nix/store/5qsfk8sm53bkzb29mdj0z92hzdpl955h-cabal2nix-2.6-doc’…

*** Downloading ‘’ (signed by ‘’) to ‘/nix/store/5qsfk8sm53bkzb29mdj0z92hzdpl955h-cabal2nix-2.6-doc’…

building path(s) ‘/nix/store/33jqmv0asmxk3sy84f5h86fikm95l9yj-user-environment’
created 251 symlinks in user environment

Step 3: Generate nix expressions for the package.

This step is taken directly from section 9.5.3 How to create Nix builds for your own private Haskell packages of the Nixpkgs Contributors Guide (Version 17.09.2378.af7e47921c4).

Convert dependencies in cabal file to a nix expression:

$ cabal2nix . > eventarelli-api.nix
$ cat eventarelli-api.nix
{ mkDerivation, aeson, base, bytestring, data-default-class, hspec
, http-types, mime-types, monad-control, mtl, mustache, QuickCheck
, random, scotty, sqlite-simple, stdenv, tagsoup, text, time
, timezone-olson, timezone-series, utf8-string, wai, wai-extra
, warp, warp-tls
mkDerivation {
  pname = "eventarelli-api";
  version = "1.1.0";
  src = ./.;
  isLibrary = true;
  isExecutable = true;
  libraryHaskellDepends = [
    aeson base bytestring mustache sqlite-simple tagsoup text time
    timezone-olson timezone-series utf8-string
  executableHaskellDepends = [
    aeson base bytestring data-default-class http-types mime-types
    monad-control mtl mustache random scotty sqlite-simple text time
    timezone-olson timezone-series utf8-string wai wai-extra warp
  testHaskellDepends = [
    base hspec mtl QuickCheck sqlite-simple time timezone-olson
  description = "Backend for Eventarelli web site";
  license = stdenv.lib.licenses.unfree;
$ cat > default.nix
{ nixpkgs ? import  {}, compiler ? "ghc802" }:
nixpkgs.pkgs.haskell.packages.${compiler}.callPackage ./eventarelli-api.nix { } 
$ cat > shell.nix
{ nixpkgs ? import  {}, compiler ? "ghc802" }:
(import ./default.nix { inherit nixpkgs compiler; }).env

Step 4: Build the package

Per nixpkgs manual, we are ready to build:

At this point, you can run nix-build to have Nix compile your project and install it into a Nix store path. The local directory will contain a symlink called result after nix-build returns that points into that location.

$ export NIX_PATH=nixpkgs=/Users/mark/.nix-defexpr/channels/nixpkgs-17.09-darwin
$ nix-build 
error: Package ‘eventarelli-api-1.1.0’ in /Users/mark/src/mycode/eventarelli/eventarelli/api/eventarelli-api.nix:8 has an unfree license (‘unfree’), refusing to evaluate.

a) For ‘nixos-rebuild‘ you can set
  { nixpkgs.config.allowUnfree = true; }
in configuration.nix to override this.

b) For ‘nix-env‘, ‘nix-build‘, ‘nix-shell‘ or any other Nix command you can add
  { allowUnfree = true; }
to ~/.config/nixpkgs/config.nix.

$ find $HOME -maxdepth 2 -type f | grep config.nix
$ vi /Users/mark/.nixpkgs/config.nix
$ cat /Users/mark/.nixpkgs/config.nix
   allowBroken = true;
   pkgs = {
     vim = {
       python = true;
   allowUnfree = true;
$ nix-build
these derivations will be built:
these paths will be fetched (264.60 MiB download, 2159.94 MiB unpacked):

Creating package registration file:
post-installation fix up
stripping (with flags -S) in /nix/store/4asrshpg52rh9dnrissailjs4xwyl9x1-eventarelli-api-1.1.0/lib  /nix/store/4asrshpg52rh9dnrissailjs4xwyl9x1-eventarelli-api-1.1.0/bin 
patching script interpreter paths in /nix/store/4asrshpg52rh9dnrissailjs4xwyl9x1-eventarelli-api-1.1.0
patching script interpreter paths in /nix/store/ghvid80b6q5hygh4a6sav2fm7lmvamwr-eventarelli-api-1.1.0-doc

Step 5: Install the package


$ rm -rf result
$ stack clean
$ nix-build

Note that if we hadn’t deleted the result directory, we would have rebuild a different hash (i.e., another version of the application) nix expression generated by cabal2nix has the line src=./.;.

Ok, let’s install it:

$ rm result
$ nix-env -f default.nix -i eventarelli-api
installing ‘eventarelli-api-1.1.0’
building path(s) ‘/nix/store/qzhcw2ypa4h1ll62qf3d77ivgbgk7gha-user-environment’
created 259 symlinks in user environment

And run it:

$ eventarelli-api 
eventarelli-api: error: export ELMARELLI_TEMPLATES=
CallStack (from HasCallStack):
  error, called at src/Main.hs:78:16 in main:Main

Poking around

Where is executable?

$ which eventarelli-api
$ ls -l $(which eventarelli-api)
lrwxr-xr-x  1 root  wheel  85 Dec 31  1969 /Users/mark/.nix-profile/bin/eventarelli-api -> /nix/store/4asrshpg52rh9dnrissailjs4xwyl9x1-eventarelli-api-1.1.0/bin/eventarelli-api

What is in /nix/store for the app?

$ tree -d -L 3 /nix/store/4asrshpg52rh9dnrissailjs4xwyl9x1-eventarelli-api-1.1.0/
├── bin
├── lib
│   ├── ghc-8.0.2
│   │   ├── eventarelli-api-1.1.0
│   │   ├── package.conf.d
│   │   └── x86_64-osx-ghc-8.0.2
│   └── links
└── nix-support

Lots of stuff! In fact: du -sh says 7.4M of stuff in 39 different files.

The ghc-8.0.2 directory holds outputs from ghc. This makes up 37 of the files.

$ tree /nix/store/4asrshpg52rh9dnrissailjs4xwyl9x1-eventarelli-api-1.1.0/lib/ghc-8.0.2 | head
├── eventarelli-api-1.1.0
│   ├── Aggregates
│   │   ├── Fan.dyn_hi
│   │   ├── Fan.hi
│   │   ├── Schedule.dyn_hi
│   │   ├── Schedule.hi
│   │   ├── Venue.dyn_hi
│   │   └── Venue.hi
│   ├── Commands.dyn_hi

The lib/links directory holds a ton of symbolic links (149 to be exact).

$ ls -l |head -2 | tail -1
lrwxr-xr-x  1 root  wheel  149 Dec 31  1969 libHSHUnit- -> /nix/store/7r29b8b1xf7h6pfxh0r1b2n5grg9bm0d-HUnit-

My app depends on HUnit, and the version it uses is /nix/store/7r29b8b1xf7h6pfxh0r1b2n5grg9bm0d-HUnit- If this dependency was upgraded, it would be a different immutable package and my application hash would be different. But my the previous version of my app would still be there under /nix/store.


Haskell nix-build: lints and generates Haddock documentation

By default, the nix build produces both hlint output as well as documentation. I had never bothered to look at those with stack build, and it was a pleasant surprise to see that output from the build.

Nix: this post just scratches the surface of what nix can do

The original nixos paper from 2010 is good read and explains the step from nixpkgs to nixos. You can find it at

There’s lots more interesting stuff with nix. For example,

What not to do with nix!

As always, this blog does not document all the mistakes I made along the way; it flows from one successful step to the next. Of course, the real world is not like that.

One of the biggest mistakes I made was to manually delete some directories under /nix/store.

Don’t do that.

Nix stores a database of packages that have been used, and although I can grep with the best of them, I could not find where it was keeping a reference to the hash I had deleted from the store.

So I popped on #nixos channel and learned the right way to delete packages is to use nix’s garbage collection, available with the nix-store --gc command.

It works like this:

You can list what profiles nix-store uses by

$ nix-store --gc --print-roots
/Users/mark/src/mycode/eventarelli/eventarelli-deploy/elmarelli-api/result -> /nix/store/z8iikvbgxclxfa213h5p32ph8qk8dlci-elmarelli-api-1.1.0
/nix/var/nix/profiles/default-1-link -> /nix/store/asgx51wzbswgg1j506pg7sq4jyclv0qn-user-environment
/nix/var/nix/profiles/default-2-link -> /nix/store/4yj1xa5dbdy1ccdhnqpma16hfi6aly10-user-environment
/nix/var/nix/profiles/per-user/mark/channels-1-link -> /nix/store/p4y8j01s28xy9z8yqschnwl0gnkbvyql-user-environment
/nix/var/nix/profiles/per-user/mark/channels-2-link -> /nix/store/cfdi9c63z5c82k002xjb937f4gl3mnv7-user-environment
/nix/var/nix/profiles/per-user/mark/profile-1-link -> /nix/store/a8jn4axpj24sa43zmi1f9gc1g9in0jr1-user-environment
/nix/var/nix/profiles/per-user/mark/profile-10-link -> /nix/store/b54m0gp0dp5n5xdai0vh570rd98q06k9-user-environment
/nix/var/nix/profiles/per-user/mark/profile-11-link -> /nix/store/mrj4nb5nsp3lvijiazccp73mjzjn3ffy-user-environment
/nix/var/nix/profiles/per-user/mark/profile-12-link -> /nix/store/qzhcw2ypa4h1ll62qf3d77ivgbgk7gha-user-environment
/nix/var/nix/profiles/per-user/mark/profile-13-link -> /nix/store/mrj4nb5nsp3lvijiazccp73mjzjn3ffy-user-environment
/nix/var/nix/profiles/per-user/mark/profile-2-link -> /nix/store/wv3qbp2jhxi21wgfz8bjir37hwydkm2k-user-environment
/nix/var/nix/profiles/per-user/mark/profile-3-link -> /nix/store/kli1w7b79j4rwc1rdbrrlnw9lcm0jhpi-user-environment
/nix/var/nix/profiles/per-user/mark/profile-4-link -> /nix/store/9i0i0j8lxc4541i09njhzfzbdmcck4fq-user-environment
/nix/var/nix/profiles/per-user/mark/profile-5-link -> /nix/store/nkq331785flvbydry3f66j9l69qz9yyf-user-environment
/nix/var/nix/profiles/per-user/mark/profile-6-link -> /nix/store/wvm0s8qv3a76kr44qxgn49v6n5df2sda-user-environment
/nix/var/nix/profiles/per-user/mark/profile-7-link -> /nix/store/33jqmv0asmxk3sy84f5h86fikm95l9yj-user-environment
/nix/var/nix/profiles/per-user/mark/profile-8-link -> /nix/store/6j9kadfschyvsqxnkx4x7k575wvqdqiv-user-environment
/nix/var/nix/profiles/per-user/mark/profile-9-link -> /nix/store/xmz347y7r53hz2j8v8gx1ayipl0481cs-user-environment
/nix/var/nix/profiles/per-user/root/channels-1-link -> /nix/store/y9bzakg6jiac6nz6z79njcyqwfy1cjp9-user-environment
/nix/var/nix/profiles/per-user/root/channels-2-link -> /nix/store/p4y8j01s28xy9z8yqschnwl0gnkbvyql-user-environment

This is getting into the innards of how nix works; every time you change your list of installed packages, nix generates a new profile for your user.

To garbage collect a package, you must delete any profile that refers to that package. You can either do this manually; for example,

$ rm /nix/var/nix/profiles/per-user/mark/profile-8-link

or use the nix-collect-garbage utility; for example:

$ nix-collect-garbage --delete-older-than 30d
removing old generations of profile /nix/var/nix/profiles/per-user/mark/profile
removing generation 3
removing generation 2
removing generation 1
removing old generations of profile /nix/var/nix/profiles/per-user/mark/channels
finding garbage collector roots…
deleting garbage…
deleting ‘/nix/store/f9hcj6p17kxx1rd3p4979ky3y85zc30y-user-environment.drv’
deleting ‘/nix/store/kli1w7b79j4rwc1rdbrrlnw9lcm0jhpi-user-environment’
deleting ‘/nix/store/y6vkk87yg28rx8mmvafy7fz964kpn1fr-env-manifest.nix’
deleting ‘/nix/store/ssnz2ngjrz0dcaax2f5sgsm099avd4sp-user-environment.drv’
deleting ‘/nix/store/wv3qbp2jhxi21wgfz8bjir37hwydkm2k-user-environment’
deleting ‘/nix/store/hcg4wdyaj75msjdl90zv3sf2jma4ir9k-user-environment.drv’
deleting ‘/nix/store/a8jn4axpj24sa43zmi1f9gc1g9in0jr1-user-environment’
deleting ‘/nix/store/4jh7mj8a7qq2ywb5cl4yprli56fjxipl-env-manifest.nix’
deleting ‘/nix/store/x316m7rgs38306sdyx7ypy4mhkcjm116-env-manifest.nix’
deleting ‘/nix/store/trash’
deleting unused links…
note: currently hard linking saves 0.00 MiB
9 store paths deleted, 0.02 MiB freed

Another way to fix things, which I figured out before getting on chat, was to run nix-build --repair as root.