With a few lines you can setup your Haskell programming environment (compilers, packages, databases, browsers, and other tools from different ecosystems) with having only the Nix Package Manager installed.

You can check this environment into source control, share it with colleagues and bring them up to speed in seconds. No manually installing apps and—if gone all the way—no “It works on my machine!” due to different package versions.
More: How to install Nix

Nix installs the terminal app nix-shell which creates an environment. In this tutorial we will see how to use it by slowly progressing from simple to intermediate usage.
tldr: Go to final usage example

Try Out Haskell Tools In Nix-Shell

Once the Nix Package Manager is installed, you need to have or create a project folder:

~$ mkdir myproject && cd myproject

In order to compile Haskell files, you need a Haskell compiler like Ghc and preferably a build tool like Cabal.

To get those tools with Nix run:

~/myproject$ nix-shell -p cabal-install ghc
Element Note
~/myproject The folder your are supposed to be in.
$ Interactive shell prompt symbol. It indicates that you can/should run the text on the right of it in your terminal.
nix-shell Helper terminal app from the Nix package manager. It creates temporary shell environements. Official Wiki
-p Flag for nix-shell. Short for --packages. It makes the following Nix packages (=usually apps) available in the resulting shell environment.
cabal-install Nix package name for the Haskell build tool (terminal app) cabal. See the official website.
ghc Nix package name for the Haskell compiler (terminal app with the same name) ghc that is used by Cabal to compile. See the official website.

This will download some necessary files and place you in a new shell environment—indicated by changing the shell prompt to [nix-shell:~/myproject]$—where the terminal apps ghc and cabal are available:

[nix-shell:~/myproject]$ cabal --version
cabal-install version 3.0.0.0

Isn’t that convenient? The best thing about nix-shell is that you can make any app in any version available that exists in the Nix Package Repository (e.g. Git and Visual Studio).

Now you can create a basic project folder structure with:

[nix-shell:~/myproject]$ cabal init

You can play around with the Main.hs Haskell file and the project definition file myproject.cabal. You can download any required Haskell package from that definition file by running cabal update, interactively test your app withcabal new-repl, build it with cabal new-build, and run it with the cabal new-run myproject command. These commands work, because Cabal can call the ghc compiler app, which is now also available in the shell environment.
More: A short intro to Cabal (Prefer the newer cabal new-* commands)

Another huge benefit of nix-shell is that it installs Ghc and Cabal locally. It doesn’t modify or pollute the rest of your system. If you run

[nix-shell:~/myproject]$ exit

you will be back in your normal shell environment where Ghc and Cabal don’t exist:

~/myproject$ cabal
cabal: command not found

Think about it: If you work on multiple project that use different versions of Ghc, packages or any other app, these versions will never come into conflict, because they only exist locally in separate environments.

Getting Started On a Haskell Project with a shell.nix file

In most projects you will need more than just a few tools. Remembering what they are and typing them out by hand into a nix-shell -p ... quickly becomes unusable. For that you can specify all your apps, tools and dependencies in a shell.nix file in your project root folder:

# minimal ~/myproject/shell.nix file
let
  myNixPkgs = import <nixpkgs> {};
in
myNixPkgs.mkShell {
  nativeBuildInputs = with myNixPkgs; [
    cabal-install # terminal app cabal
    ghc # Haskell compiler
  ];
}
Element Note
let .. in .. Define variables in the let-part that you want to use in the in-part.
myNixPkgs Custom variable for our extended collection with all available Nix packages (more than 60000). Think of it as a huge Json object { cabal-install:.., ghc:.., git:.. } of packages.
import Load the content of the file represented by the next argument.
<nixpkgs> Short Nix notation to a file from the Nix package manager that holds all Nix packages.
{} Empty argument to <nixpkgs>, which is actually not a list, but a function that returns a list when you give an argument.
import <nixpkgs> {} Read it as (import <nixpkgs>) {}, which first loads the function (import <nixpkgs>) that will give you a package list when you give it an argument. And then you give it an argument {} to actually get the package list.
mkShell A helper function from myNixPkgs to create a shell environment. So actually, myNixPkgs not only contains packages but also helper functions.
nativeBuildInputs List of Nix packages that you should be able to call from the terminal in the shell environment. There are many other things you can configure.
with myNixPkgs; [ghc git] Short Nix notation for [myNixPkgs.ghc myNixPkgs.git] for readability.
[ghc git] A list in the Nix language. Nix doesn’t use commas in between!

Of course you can add other apps and dev tools to your environment:

# minimal ~/myproject/shell.nix file
...
  nativeBuildInputs = with myNixPkgs; [
    # General dev tools
    git 
    ripgrep # Fast text search alternative to grep. Terminal command: "rg"
    vscode # Visual Studio Code. Terminal command: "code"
    # Haskell dev tools
    cabal-install # terminal command: "cabal" 
    ghc # Haskell compiler
    haskellPackages.ghcid # Haskell Code checker
  ];
...

These apps and tools will be available after running nix-shell.


This file is written in the Nix (programming) language and defines the same shell environment (where Cabal and Ghc exist) as the nix-shell -p cabal-install ghc command . You can get into this environment with:

~/myproject$ nix-shell

If you don’t pass any arguments to nix-shell, it will automatically load the environment from the shell.nix file if it exists.

warning

This setup works for most Haskell packages but not all.

Some popular Haskell packages like digest for cryptographically strong hashing or JuicyPixels for image manipulation require some external C libraries like zlib.h (for fast compression algorithms) that our currently loaded Ghc doesn’t know about. So, if you mention those packages as dependencies in your myproject.cabal file, you will get a compile error that a library is missing:

[nix-shell:~/myproject]$ cabal new-build
...
cabal: Missing dependency on a foreign library:
* Missing (or bad) header file: zlib.h
* Missing (or bad) C library: z
...

Usually, you would now have to install those libraries by hand. To learn how to avoid any manual work by letting Nix manage those Haskell packages dependencies, read on.

warning

Currently, only Cabal and Ghc are managed by Nix. Haskell packages are managed by Cabal, which is bad for reproducibility.

When something is managed by Nix, Nix decides where to download from, what version to use and where to store them on your system to not to interfer with the rest of your system. However, when you call cabal update, Cabal decides where to download packages from, which version to use and where to install them. Nix is very good at creating independent environments and reproducibility: If done right, Nix can be configured to recreate an bit-by-bit identical environment for the same shell.nix file. Cabal is less capable. For example, it currently installs packages “globally” into a folder like ~/.cabal/packages.

Let Nix manage Haskell packages for Cabal

You can let Nix manage not only your tools like Ghc and Cabal, but also dependencies like Haskell packages. Nix will also take care to download and provide any external libraries that a package may need. So you don’t need to do that manually.

If you have followed along, you should already have a myproject.cabal file in your project root folder. Then you can just replace your shell.nix with the following:

# extended "~/myproject/shell.nix" file
let
  myNixPkgs = import <nixpkgs> {
    overlays = [myNixPkgsOverlay];
  };

  myNixPkgsOverlay = (nixSelf: nixSuper: {
    myHaskellPackages = nixSelf.haskellPackages.override (oldHaskellPkgs: {
      overrides = nixSelf.lib.composeExtensions (oldHaskellPkgs.overrides or (_: _: {}))  myHaskellPkgsOverlay;
    });
  });

  myHaskellPkgsOverlay = (hSelf: hSuper: {
    # "myproject" is the first part of the "myproject.cabal" project definition file
    myProject = hSelf.callCabal2nix "myproject" ./. {};
  });
in
myNixPkgs.myHaskellPackages.myProject.env
Element Note
let .. in .. Define variables in the let-part that you want to use in the in-part.
myNixPkgs Custom variable for our extended collection with all available Nix packages (more than 60000). Think of it as a huge Json object { myHaskellPackages:.., ghc:.., git:.. } of packages now containing our extended Haskell package list.
import Load the content of the file represented by the next argument.
<nixpkgs> Short Nix notation to a file from the Nix package manager that holds all Nix packages.
{ overlays = ...} Overlay argument to <nixpkgs> to modify/extend it. <nixpkgs> is actually not a collection, but a function that returns a collection when you give it an argument.
import <nixpkgs> { ...} Read it as (import <nixpkgs>) {}, which first loads the function (import <nixpkgs>) that will give you a package collection when you give it an argument. And then you give it an argument {...} to actually get the collection.
myNixPkgsOverlay Overlay function to add a new Nix package myHaskellPackages to the already huge collection of Nix packages.
myHaskellPackages The new package that we want to add to Nix packages. It is actually not a single package, but itself a collection of Nix Haskell packages. It is the default collection nixpkgs.haskellPackages plus our new Haskell package myProject, which we add again through an overlay on haskellPackages.
haskellPackages.override A helper function to create a copy of haskellPackages with the desired modifications. Our modification is that we add our myProject Haskell package through the myHaskellPkgsOverlay overlay.
myHaskellPkgsOverlay Overlay function to add a new Nix Haskell package myproject to the collection of Nix Haskell packages.
myproject Our new Nix Haskell package.
callCabal2nix Helper function from nixpkgs.haskellPackges to create a Nix Haskell package from the myproject.cabal file and let Nix manage your Haskell dependencies instead of Cabal. It takes 3 arguments: The name of the Cabal file, where to look for it (./. is the current folder) and a collection of options (no options {}).
myproject.env Definition of a shell environment where all the Haskell dependencies of that package are available to the terminal. Haskell packages by convention have an env attribute (normal Nix packages don’t).

An interesting concept here is that of an overlay: It is a Nix pattern to sanely modify a package list where the packages can have each other as dependencies. By convention it is a function with two arguments: a self and a super which both are lists of Nix packages (so there is e.g. self.ghc and super.ghc), and returns a set of packages that you want to add or modify. Rule of thumb: If you want to use Ghc as a dependency or create a new Ghc variant package, use self.ghc. If you want to modify Ghc itself, use super.ghc as in { ghc = super.ghc.override... }.


The important line that tells Nix to manage your Haskell packages is:

myProject = hSelf.callCabal2nix "myproject" ./. {};

It creates a new Haskell package myproject that requires those packages as dependencies which you mention in your myproject.cabal file. By convention Haskell packages (and those created with callCabal2nix) have an attribute .env which contains a description for a shell environment with all necessary dependencies to develop that package. This environment is what we load with the last line:

myNixPkgs.myHaskellPackages.myProject.env

This includes Ghc, but unfortuneately not Cabal, because Cabal is not strictly necessary to compile a package/project. You could do it with Ghc alone. To see how to add Cabal and other tools to this environment, read on.

info

If you haven’t created/initialized a project yet, you can run

~/myproject$ nix-shell -p cabal-install ghc --run "cabal init"

to create a myproject.cabal file. This creates a temporary shell environment where Cabal and Ghc exist, runs the given command cabal init inside it, and exits.

info

Due to the compactness of the Nix language we could have reduced the whole extended shell.nix file to a single line:

((import <nixpkgs> {}).haskellPackages.callCabal2nix "myproject" ./. {}).env

However, this is hard to read and hard to extend with other customizations.

Final Bootstrable shell.nix File

A convenient shell.nix to start with is the following:

# full "~/myproject/shell.nix" file
let
  myNixPkgs = import <nixpkgs> {
    overlays = [myNixPkgsOverlay];
  };

  myNixPkgsOverlay = (nixSelf: nixSuper: {
    myHaskellPackages = nixSelf.haskellPackages.override (oldHaskellPkgs: {
      overrides = nixSelf.lib.composeExtensions (oldHaskellPkgs.overrides or (_: _: {}))  myHaskellPkgsOverlay;
    });
  });

  myHaskellPkgsOverlay = (hSelf: hSuper: {
    # "myproject" is the first part of the "myproject.cabal" project definition file
    myProject = hSelf.callCabal2nix "myproject" ./. {};
  });
  
  myDevTools = with myNixPkgs; [
    cabal-install 
    haskellPackages.ghcid
  ];

  myShellHook = ''
    alias repl="cabal new-repl"
  '';
in
myNixPkgs.myHaskellPackages.myProject.env.overrideAttrs (oldEnv: {
  nativeBuildInputs = oldEnv.nativeBuildInputs ++ myDevTools;
  shellHook = myShellHook;
})
Element Note
let .. in .. Define variables in the let-part that you want to use in the in-part.
myNixPkgs Custom variable for our extended collection with all available Nix packages (more than 60000). Think of it as a huge Json object { myHaskellPackages:.., ghc:.., git:.. } of packages now containing our extended Haskell package list.
import Load the content of the file represented by the next argument.
<nixpkgs> Short Nix notation to a file from the Nix package manager that holds all Nix packages.
{ overlays = ...} Overlay argument to <nixpkgs> to modify/extend it. <nixpkgs> is actually not a collection, but a function that returns a collection when you give it an argument.
import <nixpkgs> { ...} Read it as (import <nixpkgs>) {}, which first loads the function (import <nixpkgs>) that will give you a package collection when you give it an argument. And then you give it an argument {...} to actually get the collection.
myNixPkgsOverlay Overlay function to add a new Nix package myHaskellPackages to the already huge collection of Nix packages.
myHaskellPackages The new package that we want to add to Nix packages. It is actually not a single package, but itself a collection of Nix Haskell packages. It is the default collection nixpkgs.haskellPackages plus our new Haskell package myProject, which we add again through an overlay on haskellPackages.
haskellPackages.override A helper function to create a copy of haskellPackages with the desired modifications. Our modification is that we add our myProject Haskell package through the myHaskellPkgsOverlay overlay.
myHaskellPkgsOverlay Overlay function to add a new Nix Haskell package myproject to the collection of Nix Haskell packages.
myproject Our new Nix Haskell package.
callCabal2nix Helper function from nixpkgs.haskellPackges to create a Nix Haskell package from the myproject.cabal file and let Nix manage your Haskell dependencies instead of Cabal. It takes 3 arguments: The name of the Cabal file, where to look for it (./. is the current folder) and a collection of options (no options {}).
myDevTools List of Nix packages we want to also make available in the shell environment.
with myNixPkgs; [ghc git] Short Nix notation for [myNixPkgs.ghc myNixPkgs.git] for readability.
[ghc git] A list in the Nix language. Nix doesn’t use commas in between!
myShellHook A shell script that is will be run in the shell environment once we enter it.
'' alias ... '' A multi-line string (two single quoutes'') in the Nix language. Here we write a shell alias so that we can writerepl instead of the long cabal new-repl command in the shell environment.
myproject.env Definition of a shell environment where all the Haskell dependencies of that package are available to the terminal. Haskell packages by convention have an env attribute (normal Nix packages don’t).
env.overrideAttrs Function (overrideAttrs means override attributes) to modify the (Json-like object) env. It takes another function (oldEnv: ..) as argument that produces a subset of those env attributes that shall be changed. In Nix all variables are immutable. SooverrideAttrs doesn’t modify env but creates a completely new environment object with the modifications.
nativeBuildInputs List of Nix packages that you should be able to call from the terminal in the shell environment. We take the old attributes oldEnv.nativeBuildInputs and add our own list of tools myDevTool with ++. There are many other things you can configure.
shellHook A shell script that is will be run in the shell environment once we enter it. It is normally empty, but we let it run our myShellHook script.

An interesting concept here is that of an overlay: It is a Nix pattern to sanely modify a package list where the packages can have each other as dependencies. By convention it is a function with two arguments: a self and a super which both are lists of Nix packages (so there is e.g. self.ghc and super.ghc), and returns a set of packages that you want to add or modify. Rule of thumb: If you want to use Ghc as a dependency or create a new Ghc variant package, use self.ghc. If you want to modify Ghc itself, use super.ghc as in { ghc = super.ghc.override... }.


info

Again, this shell.nix files requires that you have myproject.cabal file. If you don’t, you can create one with:

~/myproject$ nix-shell -p cabal-install ghc --run "cabal init"
Tags: nix programming environment setup haskell beginner

Malte Neuss

Java Software Engineer by day, Haskell enthusiast by night.

Other Posts In Series

Just Enough Nix For Programming

How to Setup Programming Environments with Nix Shell

With a few lines you can setup programming environments (compilers, packages, databases, browsers, and other tools from different ecosystems) with having only the Nix Package Manager installed.

Read More

Just Enough Nix For Programming

How to Search For Apps and Tools within Nix Packages

Nix is very capable of managing dependencies and creating programming environments. Everything is bundled up as Nix packages. However, when we look for an app in a specific version, it can be difficult to find the right Nix package name.

Read More

Just Enough Nix For Programming

Declarative App Builds and Environments: How to create Nix Derivations

Derivations are recipes to build and distribute apps of any ecosystem in Nix. They are similar to Docker files but better, because the Nix programming language allows custom functions like mkDerivation and mkShell to hide complex details.

Read More