diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 72cdf71618d04f803be6aafb18b31666cd3eb086..017ec267abba2fa75a07f3742e13ed154c57a05d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,54 +1,94 @@
+stages:
+  - docker
+  - build
+  - test
+  - deploy
+
 default:
-  tags: ['docker']
-  image: registry.gitlab.inria.fr/solverstack/docker/distrib
-  before_script:
-    - wget https://github.com/Kitware/CMake/releases/download/v3.22.0/cmake-3.22.0-linux-x86_64.tar.gz
-    - tar xvf cmake-3.22.0-linux-x86_64.tar.gz
-    - export PATH="$PWD/cmake-3.22.0-linux-x86_64/bin/:$PATH"
+  image: $CI_REGISTRY_IMAGE
+  tags: ['ci.inria.fr', 'linux', 'large'] # gitlab instance runner (i.e. docker on linux)
+  interruptible: true
 
 variables:
   GIT_SUBMODULE_STRATEGY: recursive
 
-stages:
-  - build_main
-  - test_main
+# push event: executed only if the source branch name is main or starts with ci-
+# merge request event: executed if the source branch name does not start with notest-
+# schedule event: not executed
+.only-main-mr:
+  rules:
+    - if: ($CI_COMMIT_BRANCH =~ /^ci-.*$/)
+    - if: ($CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH) &&
+          (($CI_PROJECT_ROOT_NAMESPACE == "solverstack"   ) ||
+           ($CI_PROJECT_ROOT_NAMESPACE == $CI_PROJECT_NAME))
+    - if: (($CI_PIPELINE_SOURCE == "merge_request_event") &&
+           ($CI_MERGE_REQUEST_SOURCE_BRANCH_NAME !~ /^notest-.*$/))
+    - if: ($CI_PIPELINE_SOURCE == "schedule" )
+      when: never
 
-build_main:
-  stage: build_main
-  interruptible: true
-  artifacts:
-    name: scalfmm_build_main
-    expire_in: 1 day
-    paths:
-      - build_main/
+docker:
+  stage: docker
+  image: docker
+  when: manual
+  before_script:
+    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
   script:
-    - mkdir build_main && cd build_main && cmake ..
+    - docker build -f Dockerfile-ci -t $CI_REGISTRY_IMAGE $PWD
+    - docker push $CI_REGISTRY_IMAGE
+
+build:
+  stage: build
+  extends: .only-main-mr
+  timeout: 2 hours
+  script:
+    - cmake -B build -DCMAKE_INSTALL_PREFIX=$PWD/install
+        -Dscalfmm_USE_MKL=ON
         -Dscalfmm_USE_MPI=OFF
         -Dscalfmm_BUILD_EXAMPLES=ON
         -Dscalfmm_BUILD_UNITS=ON
         -Dscalfmm_BUILD_TOOLS=ON
         -Dscalfmm_BUILD_CHECK=ON
         -DCMAKE_VERBOSE_MAKEFILE=ON
-        -DCMAKE_C_COMPILER=gcc
-        -DCMAKE_CXX_COMPILER=g++
-    - make -j5 2>&1 |tee scalfmm-main-build.log
-    - make examples 2>&1 |tee scalfmm-main-examples-build.log
-    - make units 2>&1 |tee scalfmm-main-units-build.log
-  only:
-    - main
+        -DCMAKE_CXX_FLAGS="-O0 -g -Wall -fPIC -fno-inline --coverage"
+    - cmake --build build -j 4 > /dev/null
+    - cmake --build build -j 4 --target examples > /dev/null
+    - cmake --build build -j 4 --target units > /dev/null
+    - cmake --install build
+  artifacts:
+    name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
+    expire_in: 1 week
+    untracked: true
 
-test_main:
-  stage: test_main
-  interruptible: true
+test:
+  stage: test
+  extends: .only-main-mr
   dependencies:
-    - build_main
+    - build
+  variables:
+    OMP_NUM_THREADS: 4
+  script:
+    - ctest --test-dir build --output-on-failure --no-compress-output --output-junit $PWD/test.xml
+    - gcovr --xml-pretty --exclude-unreachable-branches --print-summary -o coverage.xml --root $PWD
+  coverage: /^\s*lines......:\s*\d+.\d+\%/
   artifacts:
-    name: scalfmm_main_test
-    expire_in: 1 day
-    paths:
-      - build_main/
+    reports:
+      junit: test.xml
+      coverage_report:
+        coverage_format: cobertura
+        path: coverage.xml
+
+pages:
+  stage: deploy
+  when: manual
+  needs: []
   script:
-    - (cd build_main && OMP_NUM_THREADS=8 ctest --no-compress-output -VV -j 8
-         -I 16,19,1 --repeat until-fail:10 --output-on-failure --stop-on-failure)
-  only:
-    - main
+    - cmake -B build
+        -Dscalfmm_USE_MKL=ON
+        -Dscalfmm_BUILD_DOC=ON
+        -DCMAKE_VERBOSE_MAKEFILE=ON
+    - cmake --build build -j 4 > /dev/null
+    - cmake --build build -j 4 --target doc > /dev/null
+    - cp -r build/docs/sphinx/ public
+  artifacts:
+    paths:
+      - public
diff --git a/Dockerfile-ci b/Dockerfile-ci
new file mode 100644
index 0000000000000000000000000000000000000000..c8e955165e95340d360697b3d77368dc3fb22cc6
--- /dev/null
+++ b/Dockerfile-ci
@@ -0,0 +1,37 @@
+#
+#  @file Dockerfile-ci
+#
+#  @copyright 2025-2025 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
+#                       Univ. Bordeaux. All rights reserved.
+#
+#  @version 3.0.0
+#  @author Florent Pruvost
+#  @date 2025-01-21
+#
+# This docker image is used to test the package during gitlab-ci pipelines.
+# It should be stored in the gitlab's project container registry:
+# https://gitlab.inria.fr/solverstack/ScalFMM/container_registry
+#
+# solverstack/docker/base docker image is defined here: https://gitlab.inria.fr/solverstack/docker
+FROM registry.gitlab.inria.fr/solverstack/docker/base
+
+USER root
+
+RUN apt-get -y update
+
+# ScalFMM dependencies: cpu kernels, mpi, ...
+RUN apt-get -y upgrade --no-install-recommends \
+    libmkl-dev \
+    sphinx-common \
+    python3-breathe \
+    python3-exhale \
+    python3-recommonmark \
+    python3-sphinx-rtd-theme
+    #libopenmpi-dev \
+    #libstarpu-dev \
+    #pybind11-dev
+
+# Set some default environment variables
+ENV MKLROOT /usr
+
+USER gitlab
diff --git a/cmake/dependencies/blas_lapack.cmake b/cmake/dependencies/blas_lapack.cmake
index 41332cc46296828d57f81e610e5655b3410d1e8d..14c1a6f8338b8553653b32507b2f00d1774b2a9c 100644
--- a/cmake/dependencies/blas_lapack.cmake
+++ b/cmake/dependencies/blas_lapack.cmake
@@ -16,7 +16,7 @@ if(${CMAKE_PROJECT_NAME}_USE_MKL)
             set(CBLAS_FOUND TRUE)
             set_target_properties(MKL::MKL PROPERTIES
                     INTERFACE_INCLUDE_DIRECTORIES
-                    "${MKL_ROOT}/include;${MKL_ROOT}/include/fftw"
+                    "${MKL_ROOT}/include;${MKL_ROOT}/include/fftw;${MKL_ROOT}/include/mkl/fftw"
             )
             list(APPEND CBLAS_TARGET MKL::MKL)
         else()
@@ -25,7 +25,7 @@ if(${CMAKE_PROJECT_NAME}_USE_MKL)
             if(DEFINED BLAS_FOUND)
                 set_target_properties(BLAS::BLAS PROPERTIES
                     INTERFACE_INCLUDE_DIRECTORIES
-                    "${MKL_ROOT}/include;${MKL_ROOT}/include/fftw"
+                    "${MKL_ROOT}/include;${MKL_ROOT}/include/fftw;${MKL_ROOT}/include/mkl/fftw"
                     )
                 list(APPEND CBLAS_TARGET BLAS::BLAS)
                 list(APPEND FUSE_LIST CBLAS)