diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..236b47d7160f841abe9e56c59f8d3604d4bf2f5b
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,77 @@
+# Use a template that makes pipelines run for the default branch, tags, and
+# all types of merge request pipelines.
+include:
+  - template: 'Workflows/MergeRequest-Pipelines.gitlab-ci.yml'
+
+# The jobs all use the `nix` CLI so make sure we have it.
+image: "nixos/nix:2.13.2"
+
+default:
+  # Choose a native Nix runner and we can use the local system's Nix store as
+  # our Nix cache.
+  tags:
+    - "nix"
+
+variables:
+  # Pick a nixpkgs to get cachix and bash from.  Nothing else
+  # Turn on some `nix` CLI features that aren't on by default.
+  NIX_CONFIG: "experimental-features = nix-command flakes"
+
+# Run the standard Haskell linter over the library and test suite sources.
+hlint:
+  script: >-
+    nix run .#hlint -- src/ test/
+
+# Use cabal to build the library and test suite and then run the test suite.
+cabal-build-and-test:
+  cache:
+    # The cache is additive.  We can aways push more objects to it.  The cabal
+    # system uses a hash mechanism so that different builds will never collide
+    # with each other.  We can probably safely add to a single cache
+    # indefinitely so we don't try to make come up with a fancy cache key.
+    # Compared to trying to use a hash of our dependencies (or, eg, the .cabal
+    # file itself) as a cache key, this saves us a lot of rebuilding when we
+    # have a minor change in dependencies or packaging metadata.  The possibly
+    # shortcoming of this approach is that the cache will grow without bounds
+    # as our dependencies change.  It would be nice if there were a way to
+    # trim stale dependencies from it periodically.
+    key: "v0-cabal"
+
+    # Even if a build fails (eg because of a test suite failure or because the
+    # job timed out because building things took too long), there will be
+    # useful build artifacts worth caching - perhaps some new dependencies
+    # will have been built, for example.  We may as well cache these.
+    when: "always"
+
+    # These paths are resolved relative to the project directory.
+    paths:
+      # Cabal normally writes this directory to $HOME.  However, we override
+      # this (in the job script) to put the directory into the project
+      # checkout, since that's the only place we can cache files from.
+      - ".cabal"
+
+  script:
+    - |
+      # Configure cabal to put its caches beneath the project directory.
+      # This is only done in CI configuration because non-CI users probably
+      # want their local cabal configuration left alone.
+      cat >cabal.project.local <<EOF
+      store-dir: $CI_PROJECT_DIR/.cabal
+      remote-repo-cache: $CI_PROJECT_DIR/.cabal/packages
+      EOF
+      nix run .#cabal-test
+
+# Use nix to build the library and test suite and run the test suite.
+nix-build-and-test:
+  script: >-
+    nix build
+
+# Force a build of the dev shell dependencies so we know they still work.
+# Also, this populates the cache with the results which is a big win for every
+# developer using our cache.
+#
+# We only have an x86_64-linux builder so we can't build the aarch64-darwin
+# shell.  Sorry...
+nix-develop:
+  script: >-
+    nix build .#devShells.x86_64-linux.default
diff --git a/flake.nix b/flake.nix
index 67acf8e28e2d2b62686fe56c49ce2f67cf03b235..83f3d7f8f4685caa40ae4277c62bfc0530df7705 100644
--- a/flake.nix
+++ b/flake.nix
@@ -37,5 +37,24 @@
           ];
       };
       packages = hslib.packages {};
+      apps.hlint = hslib.apps.hlint {};
+
+      # Using the working directory of `nix run`, do a build with cabal and
+      # then run the test suite.
+      apps.cabal-test = {
+        type = "app";
+        program = "${
+          pkgs.writeShellApplication {
+            name = "cabal-build-and-test";
+            runtimeInputs = with pkgs; [pkg-config haskell.compiler.${ghcVersion} cabal-install];
+
+            text = ''
+              cabal update hackage.haskell.org
+              cabal build tests
+              cabal run tests
+            '';
+          }
+        }/bin/cabal-build-and-test";
+      };
     });
 }