diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index fafc6d425f42dd00bf833ff268028e6c7709efdd..25fe61a5aa2ca0b584765781a56c22739baa25df 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -54,6 +54,7 @@ include:
   - local: '/scripts/ci/jobs/checks.yml'
   # - template: 'Code-Quality.gitlab-ci.yml' # see !3053
   - local: '/scripts/ci/jobs/code-quality.yml'
+  - local: '/scripts/ci/jobs/code-coverage.yml'
   - local: '/scripts/ci/jobs/build-gui-linux.yml'
   - local: '/scripts/ci/jobs/build-gui-win.yml'
   - local: '/scripts/ci/jobs/build-gui-mac.yml'
diff --git a/scripts/ci/extends/template-build-linux.yml b/scripts/ci/extends/template-build-linux.yml
index 346ff35eb4522926f63f8bb17f817feb60cfe5fb..089ca30ef94560f952ef866ac35ef0206a1dbf0d 100644
--- a/scripts/ci/extends/template-build-linux.yml
+++ b/scripts/ci/extends/template-build-linux.yml
@@ -15,9 +15,14 @@
     - cmake -S . --preset=$CMAKE_PRESET $CMAKE_ARGS -DOGS_BUILD_PROCESSES=$BUILD_PROCESSES
     - build_dir=${BUILD_DIR:-build/$CMAKE_PRESET}
     - cd $build_dir
-    - $BUILD_CMD_PREFIX cmake --build . --target package > >(tee make.output)
-    - if [ "$BUILD_TESTS" = true ] ; then $BUILD_CMD_PREFIX cmake --build . --target tests ; fi
-    - if [ "$BUILD_CTEST" = true ] ; then $BUILD_CMD_PREFIX cmake --build . --target ctest ; fi
-    - if [ "$BUILD_CTEST_LARGE" = true ] ; then $BUILD_CMD_PREFIX cmake --build . --target ctest-large ; fi
-    - if [ "$BUILD_CTEST" = true ] ; then cp Testing/**/Test.xml Tests/ctest.xml ; fi
-    - if [ "$BUILD_CTEST_LARGE" = true ] ; then cp Testing/**/Test.xml Tests/ctest-large.xml ; fi
+    - |
+      if [[ -z "$TARGETS" ]]; then
+          $BUILD_CMD_PREFIX cmake --build . --target package > >(tee make.output)
+          if [ "$BUILD_TESTS" = true ] ; then $BUILD_CMD_PREFIX cmake --build . --target tests ; fi
+          if [ "$BUILD_CTEST" = true ] ; then $BUILD_CMD_PREFIX cmake --build . --target ctest ; fi
+          if [ "$BUILD_CTEST_LARGE" = true ] ; then $BUILD_CMD_PREFIX cmake --build . --target ctest-large ; fi
+          if [ "$BUILD_CTEST" = true ] ; then cp Testing/**/Test.xml Tests/ctest.xml ; fi
+          if [ "$BUILD_CTEST_LARGE" = true ] ; then cp Testing/**/Test.xml Tests/ctest-large.xml ; fi
+      else
+          $BUILD_CMD_PREFIX cmake --build . --target ${TARGETS} > >(tee make.output)
+      fi
diff --git a/scripts/ci/jobs/code-coverage.yml b/scripts/ci/jobs/code-coverage.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c253ab3175460f7de273d5fbd4caa448ef7309bc
--- /dev/null
+++ b/scripts/ci/jobs/code-coverage.yml
@@ -0,0 +1,17 @@
+code coverage:
+  stage: check
+  extends:
+    - .template-build-linux
+  tags: [shell, envinf]
+  variables:
+    CMAKE_PRESET: coverage
+    TARGETS: testrunner_coverage
+  before_script:
+    # HACK to easier linking to the generated pages
+    - echo '<meta http-equiv="REFRESH" content="0;URL=build/coverage/testrunner_coverage/index.html">' >> Coverage.html
+  artifacts:
+    expose_as: 'Coverage'
+    paths:
+      - Coverage.html
+      - build/coverage/testrunner_coverage
+    expire_in: 1 week