Package Management
IHP uses the Nix Package Manager for managing its dependencies, such as Haskell packages, compilers, and even Postgres. You can find more about the motivation on using nix on the IHP blog.
Internally IHP uses devenv.sh together with Nix flakes.
Using a Haskell Package
To install a Haskell package from Hackage (the standard Haskell package registry), open the flake.nix file and append the package name.
Let’s say we want to use mmark for rendering markdown in our project. The mmark library is not bundled with IHP, so we need to add this package as a dependency to our project. For that open the flake.nix. The file will look like this:
{
inputs = {
ihp.url = "github:digitallyinduced/ihp/1.1";
nixpkgs.follows = "ihp/nixpkgs";
flake-parts.follows = "ihp/flake-parts";
devenv.follows = "ihp/devenv";
systems.follows = "ihp/systems";
};
outputs = inputs@{ ihp, flake-parts, systems, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = import systems;
imports = [ ihp.flakeModules.default ];
perSystem = { pkgs, ... }: {
ihp = {
enable = true;
projectPath = ./.;
packages = with pkgs; [
# Native dependencies, e.g. imagemagick
];
haskellPackages = p: with p; [
# Haskell dependencies go here
p.ihp
cabal-install
base
wai
text
hlint
];
};
};
};
}
In the list following haskellPackages we can see a few haskell dependencies already. We have to append mmark to the list:
{
inputs = {
ihp.url = "github:digitallyinduced/ihp/1.1";
nixpkgs.follows = "ihp/nixpkgs";
flake-parts.follows = "ihp/flake-parts";
devenv.follows = "ihp/devenv";
systems.follows = "ihp/systems";
};
outputs = inputs@{ ihp, flake-parts, systems, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = import systems;
imports = [ ihp.flakeModules.default ];
perSystem = { pkgs, ... }: {
ihp = {
enable = true;
projectPath = ./.;
packages = with pkgs; [
# Native dependencies, e.g. imagemagick
];
haskellPackages = p: with p; [
# Haskell dependencies go here
p.ihp
cabal-install
base
wai
text
hlint
mmark
];
};
};
};
}
Stop the development server by hitting CTRL + C. Your terminal should now automatically start rebuilding the development environment. This is triggered by direnv detecting that the flake.nix has changed.
Run devenv up again to start the development server, and mmark should now be used as expected.
Using a Native Dependency
Sometimes your project uses some other software tool that is not included with IHP by default. Because we’re using nix, we can easily manage that dependency for our project.
Let’s say we want to add ImageMagick to transform and resize images uploaded by the users of our application.
All dependencies of our project are listed in flake.nix at the root of the project directory. The file looks like this:
{
{
inputs = {
ihp.url = "github:digitallyinduced/ihp/1.1";
nixpkgs.follows = "ihp/nixpkgs";
flake-parts.follows = "ihp/flake-parts";
devenv.follows = "ihp/devenv";
systems.follows = "ihp/systems";
};
outputs = inputs@{ ihp, flake-parts, systems, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = import systems;
imports = [ ihp.flakeModules.default ];
perSystem = { pkgs, ... }: {
ihp = {
enable = true;
projectPath = ./.;
packages = with pkgs; [
# Native dependencies, e.g. imagemagick
];
haskellPackages = p: with p; [
# Haskell dependencies go here
p.ihp
cabal-install
base
wai
text
hlint
];
};
};
};
}
}
We now just have to add imagemagick to packages:
{
inputs = {
ihp.url = "github:digitallyinduced/ihp/1.1";
nixpkgs.follows = "ihp/nixpkgs";
flake-parts.follows = "ihp/flake-parts";
devenv.follows = "ihp/devenv";
systems.follows = "ihp/systems";
};
outputs = inputs@{ ihp, flake-parts, systems, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = import systems;
imports = [ ihp.flakeModules.default ];
perSystem = { pkgs, ... }: {
ihp = {
enable = true;
projectPath = ./.;
packages = with pkgs; [
# Native dependencies, e.g. imagemagick
imagemagick
];
haskellPackages = p: with p; [
# Haskell dependencies go here
p.ihp
cabal-install
base
wai
text
hlint
];
};
};
};
}
Stop the development server by hitting CTRL + C. Your terminal should now automatically start rebuilding the development environment and install ImageMagick. This is triggered by direnv detecting that the flake.nix has changed.
When you are inside the project with your terminal, you can also call imagemagick to see that it’s available.
You can look up the package name for the software you depend on inside the nixpkgs repository. Just open it on GitHub and use the GitHub search to look up the package name.
Advanced
Using a Different Version of a Haskell Package
Let’s say we want to use the google-oauth2 package from hackage to add Google OAuth to our application. We can install the package in our project by adding it to haskellPackages in our flake.nix:
{
inputs = {
ihp.url = "github:digitallyinduced/ihp/1.1";
nixpkgs.follows = "ihp/nixpkgs";
flake-parts.follows = "ihp/flake-parts";
devenv.follows = "ihp/devenv";
systems.follows = "ihp/systems";
};
outputs = inputs@{ ihp, flake-parts, systems, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = import systems;
imports = [ ihp.flakeModules.default ];
perSystem = { pkgs, ... }: {
ihp = {
enable = true;
projectPath = ./.;
packages = with pkgs; [
# Native dependencies, e.g. imagemagick
];
haskellPackages = p: with p; [
# Haskell dependencies go here
p.ihp
cabal-install
base
wai
text
hlint
google-oauth2
];
};
};
};
}
This will install version 0.3.0.0 of the google-oauth2 package, as this is the latest version available in the package set used by IHP. For our specific application requirements, we want to use version 0.2.2, an older version of this package.
The package definition comes from nixpkgs. To override it, we will point our flake.nix to our own fork of nixpkgs. First (with the default nixpkgs in flake.nix) we find the current nixpkgs revision for our version of IHP by running nix flake metadata --inputs-from . nixpkgs:
$ nix flake metadata --inputs-from . nixpkgs
Resolved URL: github:NixOS/nixpkgs/9cb344e96d5b6918e94e1bca2d9f3ea1e9615545?narHash=sha256-gKlP0LbyJ3qX0KObfIWcp5nbuHSb5EHwIvU6UcNBg2A%3D
Locked URL: github:NixOS/nixpkgs/9cb344e96d5b6918e94e1bca2d9f3ea1e9615545?narHash=sha256-gKlP0LbyJ3qX0KObfIWcp5nbuHSb5EHwIvU6UcNBg2A%3D
Description: A collection of packages for the Nix package manager
Revision: 9cb344e96d5b6918e94e1bca2d9f3ea1e9615545
Last modified: 2025-08-20 17:33:59
Fingerprint: a7682548550237130fe9f60275c2be495005dce98ced24e788bc9b3bf74fc91f
This tells us that IHP uses nixpkgs at revision 9cb344e96d5b6918e94e1bca2d9f3ea1e9615545. We will now create a shallow clone at that revision (nixpkgs is big and shallow clones are faster):
-
Hit fork on https://github.com/NixOS/nixpkgs and open https://github.com/YOURUSER/nixpkgs/tree/9cb344e96d5b6918e94e1bca2d9f3ea1e9615545
-
Click the tag/branch selector and give it a name like
ihp-1.4and click the “Create branch ihp-1.4 from c6a788f” button -
Shallow clone your fork at that branch, and give it some new name for the changes you will put on top:
git clone --depth 3 -b ihp-1.4 https://github.com/YOURUSER/nixpkgs cd nixpkgs git checkout -b downgraded-google-oauth2
To use the older version of the package we need to override the package definition. To do this, enter a temporary Nix shell with cabal2nix and cabal-install:
nix-shell -p cabal2nix -p cabal-install
Now inside this shell, we can use cabal2nix to get a nix package definition for the older version of google-oauth2:
cabal2nix cabal://google-oauth2-0.2.2
(You may need to run cabal update first, so that your local cabal cache of Hackage is updated.)
This will output a new nix package definition like this:
{ mkDerivation, aeson, base, bytestring, hspec, HTTP, http-conduit
, http-types, load-env, stdenv
}:
mkDerivation {
pname = "google-oauth2";
version = "0.2.2";
sha256 = "0n408kh48d7ky09j9zw9ad4mhbv1v7gq6i3ya4f6fhkjqqgw8c1j";
libraryHaskellDepends = [
aeson base bytestring HTTP http-conduit
];
testHaskellDepends = [
base bytestring hspec http-conduit http-types load-env
];
description = "Google OAuth2 token negotiation";
license = stdenv.lib.licenses.mit;
}
Now open pkgs/development/haskell-modules/hackage-packages.nix and look for the line
"google-oauth2" = callPackage (
Remove the contents of callPackage(…) and instead insert the package definition you got from cabal2nix, giving e.g.
"google-oauth2" = callPackage (
{ mkDerivation, aeson, base, bytestring, hspec, HTTP, http-conduit
, http-types, load-env, stdenv
}:
mkDerivation {
pname = "google-oauth2";
version = "0.2.2";
sha256 = "0n408kh48d7ky09j9zw9ad4mhbv1v7gq6i3ya4f6fhkjqqgw8c1j";
libraryHaskellDepends = [
aeson base bytestring HTTP http-conduit
];
testHaskellDepends = [
base bytestring hspec http-conduit http-types load-env
];
description = "Google OAuth2 token negotiation";
license = stdenv.lib.licenses.mit;
}
) { };
Go back to your project directory and run nix flake update. This will try to install the new google-oauth2 package in the expected version 0.2.2.
This step might fail with an error like Encountered missing or private dependencies:
$ nix-shell
these derivations will be built:
/nix/store/9a0bnhr5fzj0a40g72j2sb1sdbcpfavj-google-oauth2-0.2.2.drv
/nix/store/nngc65qyw3bdf6zn84ca0jpxz19mmcv6-ghc-8.8.3-with-packages.drv
building '/nix/store/9a0bnhr5fzj0a40g72j2sb1sdbcpfavj-google-oauth2-0.2.2.drv'...
setupCompilerEnvironmentPhase
Build with /nix/store/102df4ic8qmmc6qqq33wwppizk9pnj1s-ghc-8.8.3.
unpacking sources
unpacking source archive /nix/store/w028wlk7f2q5axsw94f5igdbyfq982pl-google-oauth2-0.2.2.tar.gz
source root is google-oauth2-0.2.2
setting SOURCE_DATE_EPOCH to timestamp 1479590674 of file google-oauth2-0.2.2/test/Spec.hs
patching sources
compileBuildDriverPhase
setupCompileFlags: -package-db=/private/var/folders/27/4cznr1bj5yd0mq5sbf3qtgv40000gn/T/nix-build-google-oauth2-0.2.2.drv-0/setup-package.conf.d -j4 -threaded
[1 of 1] Compiling Main ( Setup.hs, /private/var/folders/27/4cznr1bj5yd0mq5sbf3qtgv40000gn/T/nix-build-google-oauth2-0.2.2.drv-0/Main.o )
Linking Setup ...
configuring
configureFlags: --verbose --prefix=/nix/store/ba7dpyhns6k6a9q6s9faxg3wc389y22b-google-oauth2-0.2.2 --libdir=$prefix/lib/$compiler --libsubdir=$abi/$libname --docdir=/nix/store/s6xdqq2wbqs006f29xkyhivrg9cjp6v5-google-oauth2-0.2.2-doc/share/doc/google-oauth2-0.2.2 --with-gcc=clang --package-db=/private/var/folders/27/4cznr1bj5yd0mq5sbf3qtgv40000gn/T/nix-build-google-oauth2-0.2.2.drv-0/package.conf.d --ghc-option=-j4 --disable-split-objs --enable-library-profiling --profiling-detail=exported-functions --disable-profiling --enable-shared --disable-coverage --enable-static --disable-executable-dynamic --disable-tests --disable-benchmarks --enable-library-vanilla --disable-library-for-ghci --extra-include-dirs=/nix/store/4i88rry2ckc928by1dzg0q9w98c9n5dr-libc++-7.1.0/include --extra-lib-dirs=/nix/store/4i88rry2ckc928by1dzg0q9w98c9n5dr-libc++-7.1.0/lib --extra-include-dirs=/nix/store/r983c8mry11l7nlnnlyfd67kic2y2dym-libc++abi-7.1.0/include --extra-lib-dirs=/nix/store/r983c8mry11l7nlnnlyfd67kic2y2dym-libc++abi-7.1.0/lib --extra-include-dirs=/nix/store/42k02ckpn4z28fpz327pr2j5h04nm8sy-compiler-rt-7.1.0-dev/include --extra-lib-dirs=/nix/store/3247xyxxpjnv876fxf7py914dd9qhsgn-compiler-rt-7.1.0/lib --extra-lib-dirs=/nix/store/3wiasc9rkx49pijdb85kkys3plhcixdb-ncurses-6.1-20190112/lib --extra-lib-dirs=/nix/store/w1v69v7w418nvsc3rxndp4cq59110bzj-libffi-3.3/lib --extra-lib-dirs=/nix/store/hrmqighslwzk32rmz00k1lw5k53f5jr3-gmp-6.2.0/lib --extra-include-dirs=/nix/store/m3yqw7xcmhvd0cmm3jh3b469kxf3yhfp-libiconv-osx-10.12.6/include --extra-lib-dirs=/nix/store/m3yqw7xcmhvd0cmm3jh3b469kxf3yhfp-libiconv-osx-10.12.6/lib --extra-framework-dirs=/nix/store/jj3pnq7i1ipd5x33xrsii3r26mdvcmir-swift-corefoundation/Library/Frameworks
Using Parsec parser
Configuring google-oauth2-0.2.2...
CallStack (from HasCallStack):
die', called at libraries/Cabal/Cabal/Distribution/Simple/Configure.hs:1022:20 in Cabal-3.0.1.0:Distribution.Simple.Configure
configureFinalizedPackage, called at libraries/Cabal/Cabal/Distribution/Simple/Configure.hs:475:12 in Cabal-3.0.1.0:Distribution.Simple.Configure
configure, called at libraries/Cabal/Cabal/Distribution/Simple.hs:625:20 in Cabal-3.0.1.0:Distribution.Simple
confHook, called at libraries/Cabal/Cabal/Distribution/Simple/UserHooks.hs:65:5 in Cabal-3.0.1.0:Distribution.Simple.UserHooks
configureAction, called at libraries/Cabal/Cabal/Distribution/Simple.hs:180:19 in Cabal-3.0.1.0:Distribution.Simple
defaultMainHelper, called at libraries/Cabal/Cabal/Distribution/Simple.hs:116:27 in Cabal-3.0.1.0:Distribution.Simple
defaultMain, called at Setup.hs:2:8 in main:Main
Setup: Encountered missing or private dependencies:
aeson >=0.8 && <0.12
builder for '/nix/store/9a0bnhr5fzj0a40g72j2sb1sdbcpfavj-google-oauth2-0.2.2.drv' failed with exit code 1
cannot build derivation '/nix/store/nngc65qyw3bdf6zn84ca0jpxz19mmcv6-ghc-8.8.3-with-packages.drv': 1 dependencies couldn't be built
error: build of '/nix/store/nngc65qyw3bdf6zn84ca0jpxz19mmcv6-ghc-8.8.3-with-packages.drv' failed
This is usually caused by a version mismatch between what the package expects and what is given by nix. You can disable version checking by “jailbreaking” the package.
To jailbreak the package open flake.nix and append "google-oauth2" to the doJailbreakPackages list:
{
inputs = {
ihp.url = "github:digitallyinduced/ihp/1.1";
nixpkgs.follows = "ihp/nixpkgs";
flake-parts.follows = "ihp/flake-parts";
devenv.follows = "ihp/devenv";
systems.follows = "ihp/systems";
};
outputs = inputs@{ ihp, flake-parts, systems, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = import systems;
imports = [ ihp.flakeModules.default ];
perSystem = { pkgs, ... }: {
ihp = {
enable = true;
projectPath = ./.;
packages = with pkgs; [
# Native dependencies, e.g. imagemagick
];
haskellPackages = p: with p; [
# Haskell dependencies go here
p.ihp
cabal-install
base
wai
text
hlint
];
doJailbreakPackages = [ "google-oauth2" ];
};
};
};
}
After that try to run devenv up.
Building Postgres With Extensions
For some applications you may want to install custom postgres extension libraries and have them available in the nix store.
For example to enable the postgis spatial
and geographic objects in PostgreSQL add
services.postgres.extensions = extensions: [ extensions.postgis ]; to your project’s flake.nix:
{
inputs = {
ihp.url = "github:digitallyinduced/ihp/v1.4";
nixpkgs.follows = "ihp/nixpkgs";
flake-parts.follows = "ihp/flake-parts";
devenv.follows = "ihp/devenv";
systems.follows = "ihp/systems";
};
outputs = inputs@{ ihp, flake-parts, systems, nixpkgs, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = import systems;
imports = [ ihp.flakeModules.default ];
perSystem = { pkgs, ... }: {
ihp = {
inherit appName;
enable = true;
projectPath = ./.;
packages = with pkgs; [];
haskellPackages = p: with p; [
# ...
];
};
devenv.shells.default = {
services.postgres.extensions = extensions: [ extensions.postgis ];
};
};
};
}
Behind the scenes this will pass a function to the postgres nix expressions postgresql.withPackages
function making the extension in your app’s nix store postgres package.
After the install you can run CREATE EXTENSION postgis; to enable all the features of the
installed extension.
Stopping Nix From Running Tests for a Haskell Dependency
Nix will try to run a test suite for a package when it’s building it from source code. Sometimes the tests fail which will stop you from installing the package. In case you want to ignore the failing tests and use the package anyway follow these steps.
Open flake.nix and append the package name to the dontCheckPackages list:
{
inputs = {
ihp.url = "github:digitallyinduced/ihp/1.1";
nixpkgs.follows = "ihp/nixpkgs";
flake-parts.follows = "ihp/flake-parts";
devenv.follows = "ihp/devenv";
systems.follows = "ihp/systems";
};
outputs = inputs@{ ihp, flake-parts, systems, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = import systems;
imports = [ ihp.flakeModules.default ];
perSystem = { pkgs, ... }: {
ihp = {
enable = true;
projectPath = ./.;
packages = with pkgs; [
# Native dependencies, e.g. imagemagick
];
haskellPackages = p: with p; [
# Haskell dependencies go here
p.ihp
cabal-install
base
wai
text
hlint
];
dontCheckPackages = [ "my-failing-package" ]; # <------- ADD YOUR PACKAGE HERE
};
};
};
}
After that, you can do nix flake update without running the failing tests.
Nixpkgs Pinning
By default, all projects use a specific version of nixpkgs pinned by IHP. You can override the nixpkgs version by replacing nixpkgs.follows with nixpkgs.url and your custom values:
{
inputs = {
ihp.url = "github:digitallyinduced/ihp/1.1";
nixpkgs.url = "github:NixOS/nixpkgs?rev=PUT YOUR CUSTOM REVISION HERE";
flake-parts.follows = "ihp/flake-parts";
devenv.follows = "ihp/devenv";
systems.follows = "ihp/systems";
};
# ...
}
Run nix flake update to rebuild the development environment.
We highly recommend only using nixpkgs versions which are provided by IHP because these are usually verified to be working well with all the packages used by IHP. Additionally, you will need to build a lot of packages from source code as they will not be available in the digitally induced binary cache.
Switching GHC Versions
IHP ships with GHC 9.10 by default. This is the recommended version and all IHP packages are binary-cached for it.
GHC 9.12 is available as an experimental opt-in. To switch, add an overlay in your project’s flake.nix that remaps ghc to ghc912:
devenv.shells.default = {
overlays = lib.mkAfter [
(final: prev: { ghc = prev.ghc912; })
];
};
This works because IHP’s overlay provides both pkgs.ghc (GHC 9.10) and pkgs.ghc912 (GHC 9.12) with all IHP packages. The extra overlay swaps the default. After adding the overlay, run direnv reload to rebuild the development environment.
Note: GHC 9.12 is not yet binary-cached. Everything will build from source, which takes significantly longer on the first build. We recommend setting up your own cachix binary cache if you use GHC 9.12 regularly.
To switch back to GHC 9.10, remove the overlay and run direnv reload.
Binary Cache
When installing IHP, the ihp-new tool will add the digitallyinduced.cachix.org binary cache to your nix system. This binary cache provides binaries for all IHP packages and commonly used dependencies for all nixpkgs versions used by IHP.
When you are using packages that are not in the binary cache and thus are compiled from source code very often, we highly recommend setting up your own cachix binary cache and use it next to the default digitallyinduced.cachix.org cache.