diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index dcdc249713ee83e28df8918753994be390e21943..de2831170f1181269e1344d58bc44ff3ea5510d4 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -33,7 +33,7 @@ stages:
 
 variables:
   # Which nixos/nix Docker image tag to use
-  DOCKER_TAG: "2.3.12"
+  DOCKER_TAG: "2.13.1"
 
   # Default this to ghc/ghc> to make it more convenient to run from the web
   # interface.
@@ -72,14 +72,8 @@ variables:
 .build-pipeline:
   extends: .build
   before_script:
-    - |
-      if [ -e store.nar ]; then
-        echo "Extracting cached Nix store..."
-        nix-store --import -vv < store.nar || echo "invalid cache"
-      else
-        echo "No cache found"
-      fi
-    - GHC_TARBALL=$(nix run -j1 --cores 1 -f ./ci -c discover_tarball.sh)
+    - !reference [.build, before_script]
+    - GHC_TARBALL=$(discover_tarball.sh)
   rules:
     - if: '$UPSTREAM_COMMIT_SHA || $UPSTREAM_PIPELINE_ID'
       when: always
@@ -100,14 +94,8 @@ test-pipeline:
 .build-master:
   extends: .build
   before_script:
-    - |
-      if [ -e store.nar ]; then
-        echo "Extracting cached Nix store..."
-        nix-store --import -vv < store.nar || echo "invalid cache"
-      else
-        echo "No cache found"
-      fi
-    - GHC_TARBALL=$(nix run -j1 --cores 1 -f ./ci -c discover_tarball.sh)
+    - !reference [.build, before_script]
+    - GHC_TARBALL=$(discover_tarball.sh)
   variables:
     UPSTREAM_BRANCH_NAME: master
     EXTRA_HC_OPTS: "-dcore-lint"
@@ -154,14 +142,8 @@ test-9.2:
 .build-9.4:
   extends: .build
   before_script:
-    - |
-      if [ -e store.nar ]; then
-        echo "Extracting cached Nix store..."
-        nix-store --import -vv < store.nar || echo "invalid cache"
-      else
-        echo "No cache found"
-      fi
-    - GHC_TARBALL=$(nix run -j1 --cores 1 -f ./ci -c discover_tarball.sh)
+    - !reference [.build, before_script]
+    - GHC_TARBALL=$(discover_tarball.sh)
   variables:
     UPSTREAM_BRANCH_NAME: ghc-9.4
     EXTRA_HC_OPTS: "-dcore-lint"
@@ -185,14 +167,8 @@ test-9.4:
 .build-9.6:
   extends: .build
   before_script:
-    - |
-      if [ -e store.nar ]; then
-        echo "Extracting cached Nix store..."
-        nix-store --import -vv < store.nar || echo "invalid cache"
-      else
-        echo "No cache found"
-      fi
-    - GHC_TARBALL=$(nix run -j1 --cores 1 -f ./ci -c discover_tarball.sh)
+    - !reference [.build, before_script]
+    - GHC_TARBALL=$(discover_tarball.sh)
   variables:
     UPSTREAM_BRANCH_NAME: ghc-9.6
     EXTRA_HC_OPTS: "-dcore-lint"
@@ -221,25 +197,25 @@ test-9.6:
 
   image: "nixos/nix:$DOCKER_TAG"
 
-  cache:
-    key: build-HEAD
-    when: always
-    paths:
-      - store.nar
-
   before_script:
     - |
-      if [ -e store.nar ]; then
-        echo "Extracting cached Nix store..."
-        nix-store --import -vv < store.nar || echo "invalid cache"
-      else
-        echo "No cache found"
-      fi
+      cat >/etc/nix/nix.conf <<EOF
+      sandbox = false
+      build-users-group = nixbld
+      trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= loony-tools:pr9m4BkM/5/eSTZlkQyRt57Jz7OMBxNSUiMC4FkcNfk=
+      substituters = https://cache.nixos.org https://cache.zw3rk.com
+      experimental-features = nix-command flakes
+      cores = 1 # to avoid resource exhaustion. See: <https://gitlab.haskell.org/ghc/head.hackage/-/issues/38>
+      EOF
+    # Equivalent to running `nix develop` but works in CI scripts.
+    # We take care to avoid setting the temp dir as this will break unsandboxed builds.
+    # See: <https://github.com/NixOS/nix/issues/1802>
+    - . <(nix print-dev-env | grep -v "export TE*MP" ) 
 
   script:
       # Install GHC
     - echo "Bindist tarball is $GHC_TARBALL"
-    - nix run -j1 --cores 1 -f ./ci -c curl -L "$GHC_TARBALL" > ghc.tar.xz
+    - curl -L "$GHC_TARBALL" > ghc.tar.xz
     - |
       nix build \
       -f ci/ghc-from-artifact.nix \
@@ -247,38 +223,24 @@ test-9.6:
       --out-link ghc
     - export GHC=`pwd`/ghc/bin/ghc
     - rm -Rf $HOME/.cabal/packages/local ci/run
-      # Build CI executable
-    - |
-      nix-build ./ci -j$CPUS --no-build-output
-      nix-store --export \
-        $(nix-store -qR --include-outputs \
-          $(nix-instantiate --quiet ./ci)) \
-      > store.nar
       # Test it
-    - nix run -f ./ci -c run-ci
+    - run-ci
 
   after_script:
     - ls -lh
     - |
-      nix run -f ./ci -c \
-      tar -cJf results.tar.xz -C ci/run \
+      tar -czf results.tar.gz -C ci/run \
       results.json logs compiler-info eventlogs
 
   artifacts:
     when: always
     paths:
-    - results.tar.xz
+    - results.tar.gz
 
 # Build and deploy a Hackage repository
 update-repo:
   stage: update-repo
 
-  cache:
-    key: build-HEAD
-    when: always
-    paths:
-      - store.nar
-
   tags:
     - x86_64-linux
     - head.hackage
@@ -293,11 +255,9 @@ update-repo:
     - if: '$CI_COMMIT_BRANCH == "master"'
 
   script:
-    - nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs
-    - nix-channel --update
-    - nix build -f ci/default.nix
-    - nix run -f ci/default.nix -c build-repo.sh extract-keys
-    - nix run -f ci/default.nix -c build-repo.sh build-repo
+    - . <(nix print-dev-env) # equivalent to running `nix develop` but work in CI scripts
+    - build-repo.sh extract-keys
+    - build-repo.sh build-repo
 
   dependencies:
     - build-master
@@ -335,8 +295,8 @@ update-branch:
   tags:
     - x86_64-linux
   script:
-    - nix run nixpkgs.git -c git remote -v
-    - nix run nixpkgs.git -c git reset --hard origin/master
-    - nix run nixpkgs.git -c git push "https://gitlab-ci-token:$PROJECT_ACCESS_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git" +HEAD:upstream-testing -o ci.skip
+    - git remote -v
+    - git reset --hard origin/master
+    - git push "https://gitlab-ci-token:$PROJECT_ACCESS_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git" +HEAD:upstream-testing -o ci.skip
   rules:
     - if: $CI_COMMIT_BRANCH == "master"
diff --git a/README.md b/README.md
index ef74cd0643753dc01e287b10003b8847510e0148..5c54182d555d52b8a194c50730f5d7aed66f2988 100644
--- a/README.md
+++ b/README.md
@@ -150,18 +150,6 @@ $ scripts/patch-tool update-patches
 This will create an appropriately-named patch in `patches/` from the edits in
 the `doctest` tree.
 
-### Usage with `nix`
-
-`default.nix` is a [Nix](https://nixos.org/nix/) expression which can be used to
-build `head.hackage` packages using GHC 8.6.1-alpha2:
-```
-$ nix build -f ./. haskellPackages.servant
-```
-It can also be used to build a compiler from a local source tree and use this to
-build `head.hackage` packages:
-```
-$ nix build -f ./. --arg ghc "(import ghc-from-source.nix {ghc-path=$GHC_TREE;})"
-```
 ### GitLab CI
 
 GHC's GitLab instance uses GitLab CI and the `head-hackage-ci` tool (contained
@@ -181,6 +169,12 @@ $ ./run-ci
 This will build all packages having patches and produce a textual summary, as
 well as a JSON file (`result.json`) describing the outcome.
 
+If you are using nix you can run:
+
+```
+nix-shell ci/ --command run-ci
+```
+
 
 ### Hackage repository
 
diff --git a/ci/default.nix b/ci/default.nix
index 76603f0c90e7338d30511381dafcc1f67fe8f2e9..e9de93735c6c8ac8f31b71dbe8cf2c253c0665f8 100644
--- a/ci/default.nix
+++ b/ci/default.nix
@@ -56,7 +56,7 @@ let
     let
       deps = [
         bash curl gnutar findutils patch rsync openssl
-        cabal-install ghc gcc binutils-unwrapped pwgen gnused
+        cabal-install haskellPackages.ghc gcc binutils-unwrapped pwgen gnused
         hackage-repo-tool overlay-tool python3 jq pkg-config
         git # cabal-install wants this to fetch source-repository-packages
       ];
@@ -66,7 +66,6 @@ let
     in
       runCommand "repo" {
         nativeBuildInputs = [ makeWrapper ];
-        cabalDepsSrc = buildDepsFragment;
       } ''
         mkdir -p $out/bin
         makeWrapper ${head-hackage-ci}/bin/head-hackage-ci $out/bin/head-hackage-ci \
@@ -94,4 +93,8 @@ let
         makeWrapper ${curl}/bin/curl $out/bin/curl
       '';
 in
-  build-repo
+  mkShell { 
+    name = "head-hackage-build-env"; 
+    buildInputs = [ build-repo ];   
+    cabalDepsSrc = buildDepsFragment;
+  }
diff --git a/ci/nix/sources.json b/ci/nix/sources.json
deleted file mode 100644
index 162748b2b1ce5a142639354a3cfe7bcdaff5dee3..0000000000000000000000000000000000000000
--- a/ci/nix/sources.json
+++ /dev/null
@@ -1,74 +0,0 @@
-{
-    "all-cabal-hashes": {
-        "branch": "hackage",
-        "description": "A repository containing all cabal files, with added metadata for package hashes",
-        "homepage": null,
-        "owner": "commercialhaskell",
-        "repo": "all-cabal-hashes",
-        "rev": "b0a2944a580a29defa7e68ebc6298bf9d851d86a",
-        "sha256": "163m68dcqc0i13s631g18sx5phgabjcvkl0nvkmcgsaqd8fnbzfc",
-        "type": "file",
-        "url": "https://github.com/commercialhaskell/all-cabal-hashes/archive/b0a2944a580a29defa7e68ebc6298bf9d851d86a.tar.gz",
-        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
-    },
-    "ghc-artefact-nix": {
-        "branch": "master",
-        "description": "Create environments with GHC HEAD artefacts",
-        "homepage": null,
-        "owner": "mpickering",
-        "repo": "ghc-artefact-nix",
-        "rev": "3684936ecde09234f51410e07ccd1c7f48d4f4ac",
-        "sha256": "17aly3bz9im5zcz9vazi3a4dqpw6xsqsk2afwb1r6mxn92wfzcys",
-        "type": "tarball",
-        "url": "https://github.com/mpickering/ghc-artefact-nix/archive/3684936ecde09234f51410e07ccd1c7f48d4f4ac.tar.gz",
-        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
-    },
-    "hackage-security": {
-        "branch": "master",
-        "description": "Hackage security framework based on TUF (The Update Framework)",
-        "homepage": "http://hackage.haskell.org/package/hackage-security",
-        "owner": "haskell",
-        "repo": "hackage-security",
-        "rev": "048844cb006eb880e256d7393928d6fd422ab6dd",
-        "sha256": "0rlx9shfa46c61pdn5inr2d3cyfkhz9xwajd5rjpfdqllkmh7c77",
-        "type": "tarball",
-        "url": "https://github.com/haskell/hackage-security/archive/048844cb006eb880e256d7393928d6fd422ab6dd.tar.gz",
-        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
-    },
-    "niv": {
-        "branch": "master",
-        "description": "Easy dependency management for Nix projects",
-        "homepage": "https://github.com/nmattia/niv",
-        "owner": "nmattia",
-        "repo": "niv",
-        "rev": "5830a4dd348d77e39a0f3c4c762ff2663b602d4c",
-        "sha256": "1d3lsrqvci4qz2hwjrcnd8h5vfkg8aypq3sjd4g3izbc8frwz5sm",
-        "type": "tarball",
-        "url": "https://github.com/nmattia/niv/archive/5830a4dd348d77e39a0f3c4c762ff2663b602d4c.tar.gz",
-        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
-    },
-    "nixpkgs": {
-        "branch": "master",
-        "description": "Nix Packages collection",
-        "homepage": "",
-        "owner": "NixOS",
-        "repo": "nixpkgs",
-        "rev": "33e0d99cbedf2acfd7340d2150837fbb28039a64",
-        "sha256": "15ll14rycfarqd7isyfms1fhszw9k36ars58gvdw3bkka5mj48cr",
-        "type": "tarball",
-        "url": "https://github.com/NixOS/nixpkgs/archive/33e0d99cbedf2acfd7340d2150837fbb28039a64.tar.gz",
-        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
-    },
-    "overlay-tool": {
-        "branch": "master",
-        "description": null,
-        "homepage": null,
-        "owner": "bgamari",
-        "repo": "hackage-overlay-repo-tool",
-        "rev": "8fdc6a32292db3b54d02b6689ec6b2af10059a0d",
-        "sha256": "1af6v50mhigl4djfixiiwmb126zjbgnnndaqjhvj11smn90ikp90",
-        "type": "tarball",
-        "url": "https://github.com/bgamari/hackage-overlay-repo-tool/archive/8fdc6a32292db3b54d02b6689ec6b2af10059a0d.tar.gz",
-        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
-    }
-}
diff --git a/ci/nix/sources.nix b/ci/nix/sources.nix
index 1938409dddb0b57d9f298046cf51875060283df2..c8e281b32a119716d960166d969c58df2c779fb6 100644
--- a/ci/nix/sources.nix
+++ b/ci/nix/sources.nix
@@ -1,174 +1,11 @@
-# This file has been generated by Niv.
-
-let
-
-  #
-  # The fetchers. fetch_<type> fetches specs of type <type>.
-  #
-
-  fetch_file = pkgs: name: spec:
-    let
-      name' = sanitizeName name + "-src";
-    in
-      if spec.builtin or true then
-        builtins_fetchurl { inherit (spec) url sha256; name = name'; }
-      else
-        pkgs.fetchurl { inherit (spec) url sha256; name = name'; };
-
-  fetch_tarball = pkgs: name: spec:
-    let
-      name' = sanitizeName name + "-src";
-    in
-      if spec.builtin or true then
-        builtins_fetchTarball { name = name'; inherit (spec) url sha256; }
-      else
-        pkgs.fetchzip { name = name'; inherit (spec) url sha256; };
-
-  fetch_git = name: spec:
-    let
-      ref =
-        if spec ? ref then spec.ref else
-          if spec ? branch then "refs/heads/${spec.branch}" else
-            if spec ? tag then "refs/tags/${spec.tag}" else
-              abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!";
-    in
-      builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; };
-
-  fetch_local = spec: spec.path;
-
-  fetch_builtin-tarball = name: throw
-    ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
-        $ niv modify ${name} -a type=tarball -a builtin=true'';
-
-  fetch_builtin-url = name: throw
-    ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
-        $ niv modify ${name} -a type=file -a builtin=true'';
-
-  #
-  # Various helpers
-  #
-
-  # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695
-  sanitizeName = name:
-    (
-      concatMapStrings (s: if builtins.isList s then "-" else s)
-        (
-          builtins.split "[^[:alnum:]+._?=-]+"
-            ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name)
-        )
-    );
-
-  # The set of packages used when specs are fetched using non-builtins.
-  mkPkgs = sources: system:
-    let
-      sourcesNixpkgs =
-        import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; };
-      hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
-      hasThisAsNixpkgsPath = <nixpkgs> == ./.;
-    in
-      if builtins.hasAttr "nixpkgs" sources
-      then sourcesNixpkgs
-      else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
-        import <nixpkgs> {}
-      else
-        abort
-          ''
-            Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
-            add a package called "nixpkgs" to your sources.json.
-          '';
-
-  # The actual fetching function.
-  fetch = pkgs: name: spec:
-
-    if ! builtins.hasAttr "type" spec then
-      abort "ERROR: niv spec ${name} does not have a 'type' attribute"
-    else if spec.type == "file" then fetch_file pkgs name spec
-    else if spec.type == "tarball" then fetch_tarball pkgs name spec
-    else if spec.type == "git" then fetch_git name spec
-    else if spec.type == "local" then fetch_local spec
-    else if spec.type == "builtin-tarball" then fetch_builtin-tarball name
-    else if spec.type == "builtin-url" then fetch_builtin-url name
-    else
-      abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
-
-  # If the environment variable NIV_OVERRIDE_${name} is set, then use
-  # the path directly as opposed to the fetched source.
-  replace = name: drv:
-    let
-      saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name;
-      ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}";
-    in
-      if ersatz == "" then drv else
-        # this turns the string into an actual Nix path (for both absolute and
-        # relative paths)
-        if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}";
-
-  # Ports of functions for older nix versions
-
-  # a Nix version of mapAttrs if the built-in doesn't exist
-  mapAttrs = builtins.mapAttrs or (
-    f: set: with builtins;
-    listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
-  );
-
-  # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
-  range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1);
-
-  # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
-  stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
-
-  # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
-  stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
-  concatMapStrings = f: list: concatStrings (map f list);
-  concatStrings = builtins.concatStringsSep "";
-
-  # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331
-  optionalAttrs = cond: as: if cond then as else {};
-
-  # fetchTarball version that is compatible between all the versions of Nix
-  builtins_fetchTarball = { url, name ? null, sha256 }@attrs:
-    let
-      inherit (builtins) lessThan nixVersion fetchTarball;
-    in
-      if lessThan nixVersion "1.12" then
-        fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
-      else
-        fetchTarball attrs;
-
-  # fetchurl version that is compatible between all the versions of Nix
-  builtins_fetchurl = { url, name ? null, sha256 }@attrs:
-    let
-      inherit (builtins) lessThan nixVersion fetchurl;
-    in
-      if lessThan nixVersion "1.12" then
-        fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
-      else
-        fetchurl attrs;
-
-  # Create the final "sources" from the config
-  mkSources = config:
-    mapAttrs (
-      name: spec:
-        if builtins.hasAttr "outPath" spec
-        then abort
-          "The values in sources.json should not have an 'outPath' attribute"
-        else
-          spec // { outPath = replace name (fetch config.pkgs name spec); }
-    ) config.sources;
-
-  # The "config" used by the fetchers
-  mkConfig =
-    { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null
-    , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile)
-    , system ? builtins.currentSystem
-    , pkgs ? mkPkgs sources system
-    }: rec {
-      # The sources, i.e. the attribute set of spec name to spec
-      inherit sources;
-
-      # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
-      inherit pkgs;
-    };
-
-in
-mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); }
+# extract sources from the flake.lock using flake-compat
+(import
+  (
+    let lock = builtins.fromJSON (builtins.readFile ../../flake.lock); in
+    fetchTarball {
+      url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
+      sha256 = lock.nodes.flake-compat.locked.narHash;
+    }
+  )
+  { src = ../../.; }
+).defaultNix.inputs
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000000000000000000000000000000000000..bd85fa477bf2bea72916889f67123d7f010b35ab
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,112 @@
+{
+  "nodes": {
+    "all-cabal-hashes": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1494499108,
+        "narHash": "sha256-vD3K/chhEGRJwSZy3WPnKnVtY+SrgypAzMkOUy8ubu8=",
+        "owner": "commercialhaskell",
+        "repo": "all-cabal-hashes",
+        "rev": "56a8e992cb300266eb6d1a181884bd765309d9be",
+        "type": "github"
+      },
+      "original": {
+        "owner": "commercialhaskell",
+        "repo": "all-cabal-hashes",
+        "type": "github"
+      }
+    },
+    "flake-compat": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1673956053,
+        "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
+        "type": "github"
+      },
+      "original": {
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "type": "github"
+      }
+    },
+    "ghc-artefact-nix": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1671037040,
+        "narHash": "sha256-2rPvuEi2V5PD4k6JqbHuhl/ciBrxq50++6XG9NfwVJ0=",
+        "owner": "mpickering",
+        "repo": "ghc-artefact-nix",
+        "rev": "3684936ecde09234f51410e07ccd1c7f48d4f4ac",
+        "type": "github"
+      },
+      "original": {
+        "owner": "mpickering",
+        "repo": "ghc-artefact-nix",
+        "type": "github"
+      }
+    },
+    "hackage-security": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1668891326,
+        "narHash": "sha256-3Oeaq4ZhHe8USes4BjIyk5VWzRTT7itvyvMVyBbj81M=",
+        "owner": "haskell",
+        "repo": "hackage-security",
+        "rev": "c37c91c48e0d21b587360534da8a852c5cdf4a74",
+        "type": "github"
+      },
+      "original": {
+        "owner": "haskell",
+        "repo": "hackage-security",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1670841420,
+        "narHash": "sha256-mSEia1FzrsHbfqjorMyYiX8NXdDVeR1Pw1k55jMJlJY=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "33e0d99cbedf2acfd7340d2150837fbb28039a64",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "33e0d99cbedf2acfd7340d2150837fbb28039a64",
+        "type": "github"
+      }
+    },
+    "overlay-tool": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1671663228,
+        "narHash": "sha256-GKeke8FT9w2u6J9aI4eN3mOV0BURHiytQAEir4/4M1Q=",
+        "owner": "bgamari",
+        "repo": "hackage-overlay-repo-tool",
+        "rev": "f60aa0d497961745a503ddea2adb0facb58376e8",
+        "type": "github"
+      },
+      "original": {
+        "owner": "bgamari",
+        "repo": "hackage-overlay-repo-tool",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "all-cabal-hashes": "all-cabal-hashes",
+        "flake-compat": "flake-compat",
+        "ghc-artefact-nix": "ghc-artefact-nix",
+        "hackage-security": "hackage-security",
+        "nixpkgs": "nixpkgs",
+        "overlay-tool": "overlay-tool"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000000000000000000000000000000000000..a024e478001e677ed1cb51760a3e54749b71ce4a
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,22 @@
+{
+  description = "head.hackage";
+  inputs.nixpkgs.url = "github:NixOS/nixpkgs";
+  inputs.ghc-artefact-nix.url = "github:mpickering/ghc-artefact-nix";
+  inputs.ghc-artefact-nix.flake = false;
+  inputs.all-cabal-hashes.url = "github:commercialhaskell/all-cabal-hashes";
+  inputs.all-cabal-hashes.flake = false;
+  inputs.hackage-security.url = "github:haskell/hackage-security";
+  inputs.hackage-security.flake = false;
+  inputs.overlay-tool.url = "github:bgamari/hackage-overlay-repo-tool";
+  inputs.overlay-tool.flake = false;
+  inputs.flake-compat.url = "github:edolstra/flake-compat";
+  inputs.flake-compat.flake = false;
+  outputs = {nixpkgs, ...}: 
+  let 
+    system = "x86_64-linux";
+    env = import ./ci/default.nix { nixpkgs = nixpkgs.legacyPackages."${system}"; };
+  in {
+    devShells."${system}".default = env;
+    hydraJobs.env."${system}" = env;
+  };
+}
diff --git a/run-ci b/run-ci
index 14da5ccb98275071326abe6d8038f12629089c97..70fbb0b82a9c193d6d8169b26346913ec381b83d 100755
--- a/run-ci
+++ b/run-ci
@@ -38,7 +38,7 @@ echo "" > run/deps.cabal.project
 if [ -n "$USE_NIX" ]; then
   # Generate native library dependency mapping
   nix eval --raw -f ./. cabalDepsSrc >> run/deps.cabal.project
-  run="nix run -f ../. -c head-hackage-ci"
+  run="head-hackage-ci"
 else
   run="head-hackage-ci"