diff --git a/.gitlab-ci.dhall b/.gitlab-ci.dhall
new file mode 100644
index 0000000000000000000000000000000000000000..9e00f3cfa93ba081da6b657766f0f2c4d30aeb3e
--- /dev/null
+++ b/.gitlab-ci.dhall
@@ -0,0 +1,56 @@
+let
+  Prelude = https://prelude.dhall-lang.org/v19.0.0/package.dhall sha256:eb693342eb769f782174157eba9b5924cf8ac6793897fc36a31ccbd6f56dafe2
+
+let
+  GitLab = https://raw.githubusercontent.com/bgamari/dhall-gitlab-ci/84c72592054cc1399fc018093d3516e4116a4d51/package.dhall
+
+let
+  GhcVersion: Type = Text
+
+let
+  linuxJob: GhcVersion -> Prelude.Map.Entry Text GitLab.Job.Type = \(ghc_version: GhcVersion) ->
+    let job: GitLab.Job.Type =
+      GitLab.Job::
+      { image = Some "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:latest"
+      , tags = Some [ "x86_64-linux" ]
+      , stage = Some "build"
+      , variables = toMap { GHC_VERSION = ghc_version }
+      , script = [ "bash .gitlab/build-linux.sh" ]
+      , cache = Some
+        { key = "ghc-${ghc_version}"
+        , paths = [ "cabal-store/" ]
+        }
+      , artifacts = Some
+        GitLab.ArtifactsSpec::
+        { paths = [ "out/haskell-language-server" ]
+        , expire_in = GitLab.Duration.fromDays 30
+        }
+      }
+
+    in Prelude.Map.keyValue GitLab.Job.Type "linux-x86_64-ghc${ghc_version}" job
+
+let
+  ghcVersions: List GhcVersion =
+    [ "8.10.3"
+    , "8.10.2"
+    , "8.10.1"
+    , "8.8.4"
+    , "8.8.3"
+    , "8.8.2"
+    , "8.8.1"
+    , "8.6.5"
+    , "8.6.4"
+    , "8.6.3"
+    , "8.6.2"
+    , "8.6.1"
+    ]
+
+let
+  top: GitLab.Top.Type =
+    GitLab.Top::
+    { stages = Some [ "build" ]
+    , jobs = Prelude.List.map GhcVersion (Prelude.Map.Entry Text GitLab.Job.Type) linuxJob ghcVersions
+    }
+
+in Prelude.JSON.renderYAML (GitLab.Top.toJSON top)
+
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8205d96f6f9c5901c360d71a2f89752153df8e3d..99341e73d7d36174ce953852073b7474c3b7fde3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,41 +1,232 @@
-.build:
-  image: registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:latest
-  tags:
-    - x86_64-linux
-
-  variables:
-    # This must be overridden
-    GHC_VERSION: unset
-
-  script:
-    - bash .gitlab/build-linux.sh
-
-  cache:
-    key: "ghc-$GHC_VERSION"
-    when: always
-    paths:
-      - cabal-store/
-
-  artifacts:
-    paths:
-      - tmp/haskell-language-server
-
-build-8.8.4:
-  extends: .build
-  variables:
-    GHC_VERSION: "8.8.4"
-
-build-8.10.1:
-  extends: .build
-  variables:
-    GHC_VERSION: "8.10.1"
-
-build-8.10.2:
-  extends: .build
-  variables:
-    GHC_VERSION: "8.10.2"
-
-build-8.10.3:
-  extends: .build
-  variables:
-    GHC_VERSION: "8.10.3"
+"linux-x86_64-ghc8.10.3":
+  "allow_failure": false
+  "artifacts":
+    "expire_in": "2592000 second"
+    "paths":
+      - "out/haskell-language-server"
+    "when": "on_success"
+  "cache":
+    "key": "ghc-8.10.3"
+    "paths":
+      - "cabal-store/"
+  "image": "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:latest"
+  "script":
+    - "bash .gitlab/build-linux.sh"
+  "stage": "build"
+  "tags":
+    - "x86_64-linux"
+  "variables":
+    "GHC_VERSION": "8.10.3"
+"linux-x86_64-ghc8.10.2":
+  "allow_failure": false
+  "artifacts":
+    "expire_in": "2592000 second"
+    "paths":
+      - "out/haskell-language-server"
+    "when": "on_success"
+  "cache":
+    "key": "ghc-8.10.2"
+    "paths":
+      - "cabal-store/"
+  "image": "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:latest"
+  "script":
+    - "bash .gitlab/build-linux.sh"
+  "stage": "build"
+  "tags":
+    - "x86_64-linux"
+  "variables":
+    "GHC_VERSION": "8.10.2"
+"linux-x86_64-ghc8.10.1":
+  "allow_failure": false
+  "artifacts":
+    "expire_in": "2592000 second"
+    "paths":
+      - "out/haskell-language-server"
+    "when": "on_success"
+  "cache":
+    "key": "ghc-8.10.1"
+    "paths":
+      - "cabal-store/"
+  "image": "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:latest"
+  "script":
+    - "bash .gitlab/build-linux.sh"
+  "stage": "build"
+  "tags":
+    - "x86_64-linux"
+  "variables":
+    "GHC_VERSION": "8.10.1"
+"linux-x86_64-ghc8.8.4":
+  "allow_failure": false
+  "artifacts":
+    "expire_in": "2592000 second"
+    "paths":
+      - "out/haskell-language-server"
+    "when": "on_success"
+  "cache":
+    "key": "ghc-8.8.4"
+    "paths":
+      - "cabal-store/"
+  "image": "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:latest"
+  "script":
+    - "bash .gitlab/build-linux.sh"
+  "stage": "build"
+  "tags":
+    - "x86_64-linux"
+  "variables":
+    "GHC_VERSION": "8.8.4"
+"linux-x86_64-ghc8.8.3":
+  "allow_failure": false
+  "artifacts":
+    "expire_in": "2592000 second"
+    "paths":
+      - "out/haskell-language-server"
+    "when": "on_success"
+  "cache":
+    "key": "ghc-8.8.3"
+    "paths":
+      - "cabal-store/"
+  "image": "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:latest"
+  "script":
+    - "bash .gitlab/build-linux.sh"
+  "stage": "build"
+  "tags":
+    - "x86_64-linux"
+  "variables":
+    "GHC_VERSION": "8.8.3"
+"linux-x86_64-ghc8.8.2":
+  "allow_failure": false
+  "artifacts":
+    "expire_in": "2592000 second"
+    "paths":
+      - "out/haskell-language-server"
+    "when": "on_success"
+  "cache":
+    "key": "ghc-8.8.2"
+    "paths":
+      - "cabal-store/"
+  "image": "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:latest"
+  "script":
+    - "bash .gitlab/build-linux.sh"
+  "stage": "build"
+  "tags":
+    - "x86_64-linux"
+  "variables":
+    "GHC_VERSION": "8.8.2"
+"linux-x86_64-ghc8.8.1":
+  "allow_failure": false
+  "artifacts":
+    "expire_in": "2592000 second"
+    "paths":
+      - "out/haskell-language-server"
+    "when": "on_success"
+  "cache":
+    "key": "ghc-8.8.1"
+    "paths":
+      - "cabal-store/"
+  "image": "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:latest"
+  "script":
+    - "bash .gitlab/build-linux.sh"
+  "stage": "build"
+  "tags":
+    - "x86_64-linux"
+  "variables":
+    "GHC_VERSION": "8.8.1"
+"linux-x86_64-ghc8.6.5":
+  "allow_failure": false
+  "artifacts":
+    "expire_in": "2592000 second"
+    "paths":
+      - "out/haskell-language-server"
+    "when": "on_success"
+  "cache":
+    "key": "ghc-8.6.5"
+    "paths":
+      - "cabal-store/"
+  "image": "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:latest"
+  "script":
+    - "bash .gitlab/build-linux.sh"
+  "stage": "build"
+  "tags":
+    - "x86_64-linux"
+  "variables":
+    "GHC_VERSION": "8.6.5"
+"linux-x86_64-ghc8.6.4":
+  "allow_failure": false
+  "artifacts":
+    "expire_in": "2592000 second"
+    "paths":
+      - "out/haskell-language-server"
+    "when": "on_success"
+  "cache":
+    "key": "ghc-8.6.4"
+    "paths":
+      - "cabal-store/"
+  "image": "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:latest"
+  "script":
+    - "bash .gitlab/build-linux.sh"
+  "stage": "build"
+  "tags":
+    - "x86_64-linux"
+  "variables":
+    "GHC_VERSION": "8.6.4"
+"linux-x86_64-ghc8.6.3":
+  "allow_failure": false
+  "artifacts":
+    "expire_in": "2592000 second"
+    "paths":
+      - "out/haskell-language-server"
+    "when": "on_success"
+  "cache":
+    "key": "ghc-8.6.3"
+    "paths":
+      - "cabal-store/"
+  "image": "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:latest"
+  "script":
+    - "bash .gitlab/build-linux.sh"
+  "stage": "build"
+  "tags":
+    - "x86_64-linux"
+  "variables":
+    "GHC_VERSION": "8.6.3"
+"linux-x86_64-ghc8.6.2":
+  "allow_failure": false
+  "artifacts":
+    "expire_in": "2592000 second"
+    "paths":
+      - "out/haskell-language-server"
+    "when": "on_success"
+  "cache":
+    "key": "ghc-8.6.2"
+    "paths":
+      - "cabal-store/"
+  "image": "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:latest"
+  "script":
+    - "bash .gitlab/build-linux.sh"
+  "stage": "build"
+  "tags":
+    - "x86_64-linux"
+  "variables":
+    "GHC_VERSION": "8.6.2"
+"linux-x86_64-ghc8.6.1":
+  "allow_failure": false
+  "artifacts":
+    "expire_in": "2592000 second"
+    "paths":
+      - "out/haskell-language-server"
+    "when": "on_success"
+  "cache":
+    "key": "ghc-8.6.1"
+    "paths":
+      - "cabal-store/"
+  "image": "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:latest"
+  "script":
+    - "bash .gitlab/build-linux.sh"
+  "stage": "build"
+  "tags":
+    - "x86_64-linux"
+  "variables":
+    "GHC_VERSION": "8.6.1"
+"stages":
+  - "build"
+"variables":
+  "GIT_SUBMODULE_STRATEGY": "normal"
diff --git a/.gitlab/build-linux.sh b/.gitlab/build-linux.sh
index 7bb82e4c54356f5ef0f3f023d26ed7bfeceb879d..456aeb68b33f0d003174fac6d3108e50b66b0b7c 100644
--- a/.gitlab/build-linux.sh
+++ b/.gitlab/build-linux.sh
@@ -8,11 +8,11 @@ curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
 source ~/.ghcup/env
 
 cabal update
-mkdir -p tmp
+mkdir -p out
 
 cabal --store-dir=$(pwd)/cabal-store v2-install \
   -j$CORES \
   exe:haskell-language-server \
   --enable-executable-static \
   --enable-split-sections \
-  --installdir=./tmp
+  --installdir=$(pwd)/out