diff --git a/.codeclimate.yml b/.codeclimate.yml
new file mode 100644
index 0000000000000000000000000000000000000000..761a90adf2de1c97afed0a92ea8102607de09957
--- /dev/null
+++ b/.codeclimate.yml
@@ -0,0 +1,7 @@
+version: "2"
+exclude_patterns:
+  - "ThirdParty/"
+  - "Tests/"
+plugins:
+  duplication:
+    enabled: false
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e72b91190af0cc27de45656f53aabd0c8e4b5c0a..e9646947359a3e58aa938c837ac97ed96743ea25 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -47,7 +47,7 @@ include:
   - local: '/scripts/ci/jobs/build-win.yml'
   - local: '/scripts/ci/jobs/build-mac.yml'
   - local: '/scripts/ci/jobs/checks.yml'
-  - template: 'Code-Quality.gitlab-ci.yml'
+  # - template: 'Code-Quality.gitlab-ci.yml' # see !3053
   - local: '/scripts/ci/jobs/code-quality.yml'
   - local: '/scripts/ci/jobs/build-gui-linux.yml'
   - local: '/scripts/ci/jobs/build-gui-win.yml'
diff --git a/scripts/ci/jobs/code-quality.yml b/scripts/ci/jobs/code-quality.yml
index 35d24fb68df66959281e12b7cad7f78e1fed1006..1c663903adf95f11ec4b65a197e174784ebd860c 100644
--- a/scripts/ci/jobs/code-quality.yml
+++ b/scripts/ci/jobs/code-quality.yml
@@ -1,5 +1,23 @@
-code_quality:
+cppcheck:
   stage: check
+  image: $CONTAINER_GCC_IMAGE
+  needs: []
+  before_script:
+    - mkdir -p build
+    - cd build
+  script: >-
+    cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DOGS_USE_CONAN=OFF \
+      -DOGS_USE_UNITY_BUILDS=OFF -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
+    bash cppcheck.sh
+  artifacts:
+    reports:
+      codequality: build/cppcheck.json
+    expire_in: 1 week
+
+# Disabled, see !3053
+.code_quality:
+  stage: check
+  tags: [docker, envinf1]
   needs: []
   rules:
   variables:
diff --git a/scripts/cmake/CppCheck.cmake b/scripts/cmake/CppCheck.cmake
index 7fd5e832dfa2edd3ea299703d23f7a74a2432515..a960a96ae0cb8e5882c4aec308b8ba6503070bf1 100644
--- a/scripts/cmake/CppCheck.cmake
+++ b/scripts/cmake/CppCheck.cmake
@@ -1,6 +1,8 @@
 if(NOT CPPCHECK_TOOL_PATH)
     return()
 endif()
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+configure_file(${PROJECT_SOURCE_DIR}/scripts/test/cppcheck.in.sh ${PROJECT_BINARY_DIR}/cppcheck.sh)
 
 if(DEFINED ENV{NUM_THREADS})
     set(CPPCHECK_THREADS -j $ENV{NUM_THREADS})
@@ -8,9 +10,11 @@ endif()
 
 add_custom_target(cppcheck
     COMMAND ${CPPCHECK_TOOL_PATH}
-        --force
+        --project=${PROJECT_BINARY_DIR}/compile_commands.json
+        --language=c++
+        --std=c++17
         --enable=all
-        # --inconclusive
+        --inconclusive
         ${CPPCHECK_THREADS}
         -i ${PROJECT_BINARY_DIR}/CMakeFiles
         -i ${PROJECT_SOURCE_DIR}/ThirdParty
diff --git a/scripts/test/cppcheck.in.sh b/scripts/test/cppcheck.in.sh
new file mode 100644
index 0000000000000000000000000000000000000000..72a922ed7113d0564c7d75e16e313bd2aaf5b2e8
--- /dev/null
+++ b/scripts/test/cppcheck.in.sh
@@ -0,0 +1,28 @@
+# Runs cppcheck with GitLab CI (CodeClimate) output
+OUTPUT_FILE=${PROJECT_BINARY_DIR}/cppcheck.json
+${CPPCHECK_TOOL_PATH} \
+        --project=${PROJECT_BINARY_DIR}/compile_commands.json \
+        --language=c++ \
+        --std=c++17 \
+        --enable=all \
+        --inconclusive \
+        -j 4 \
+        -i ${PROJECT_BINARY_DIR}/CMakeFiles \
+        -i ${PROJECT_SOURCE_DIR}/ThirdParty \
+        -i ${PROJECT_SOURCE_DIR}/Applications/DataExplorer \
+        -i ${PROJECT_SOURCE_DIR}/Tests \
+        --template='{\n  "description": "{message}",\n  "location": {\n    "path": "{file}",\n    "lines": {\n      "begin": {line}\n    }\n  }\n},' \
+        --output-file=$OUTPUT_FILE \
+
+echo "$( \
+  # add brackets
+  printf '[\n'; \
+  cat $OUTPUT_FILE | \
+  # strip source code absolute path
+  sed 's|${PROJECT_SOURCE_DIR}/||' | \
+  # escape strings
+  sed 's/string literal "\(.*\)" to/string literal \\"\1\\" to/g' | \
+  # remove last comma
+  sed '$s/,$//'; \
+  printf ']\n')" \
+  > $OUTPUT_FILE