diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 02f3cf8662ba081b7f6163e750eb5d9269c4d934..07f65a409b059f43319b5729677a6e8b3c6a922d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,35 +1,58 @@
 image: vlafeychine/rust
 
+stages:
+  - check
+  - build
+  - tests
+  - docs
+
 variables:
   CARGO_HOME: "$CI_PROJECT_DIR/.cache/cargo/"
 
 cache:
-  key:
-    files:
-      - Cargo.lock
+  key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
   paths:
     - .cache/cargo/
     - target/
 
 format:
+  stage: check
   script:
     - cargo fmt --check
 
 lint:
+  stage: check
   script:
     - cargo clippy --all-targets --all-features -- -D warnings
 
 build:
-  needs: [format, lint]
+  stage: build
   script:
     - cargo build
 
-test:
-  needs: [build]
+tests:
+  stage: tests
+  variables:
+    CARGO_INCREMENTAL: 0
+    LLVM_PROFILE_FILE: "coverage-%p-%m.profraw"
+    RUSTDOCFLAGS: "-Cpanic=abort"
+    RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
+
   script:
     - cargo test
+    - grcov . -s . --binary-path ./target/debug/ -t cobertura --branch --ignore "*cargo*" --ignore-not-existing -o coverage.xml 
+    - sed -n '3p' coverage.xml | grep -oE '[a-z-]+="[0-9](.[0-9]+)?"' | sed -r 's/(branch-rate|line-rate)="(0|(1)).([0-9]{2})([0-9]{2})[0-9]*"/\1="\3\4.\5%"/'
+
+  artifacts:
+    reports:
+      coverage_report:
+        coverage_format: cobertura
+        path: coverage.xml
+
+  coverage: '/^branch-rate="\d+.\d+%"/'
 
 docs:
+  stage: docs
   only:
     refs:
       - main
diff --git a/flake.nix b/flake.nix
index d8a2e72f1e0f6e2453e129c10f34aa0540621f3c..ddfe9fdcc1fd154100a1fe0b878152c0abbc27f5 100644
--- a/flake.nix
+++ b/flake.nix
@@ -32,8 +32,7 @@
           };
 
           docker-ci = let
-            rust-ci =
-              rust.minimal.override { extensions = [ "clippy" "rustfmt" ]; };
+            rust-ci = rust.minimal.override { extensions = [ "clippy" "llvm-tools-preview" "rustfmt" ]; };
           in pkgs.dockerTools.buildImage {
             name = "proost-ci";
 
@@ -41,7 +40,7 @@
 
             copyToRoot = pkgs.buildEnv {
               name = "proost-dependencies";
-              paths = (with pkgs; [ coreutils gcc openssh rust-ci ])
+              paths = (with pkgs; [ coreutils gcc gnugrep gnused grcov openssh rust-ci ])
                 ++ (with pkgs.dockerTools; [ binSh caCertificates fakeNss ]);
               pathsToLink = [ "/bin" "/etc" ];
             };
@@ -52,8 +51,7 @@
 
         devShells.default = pkgs.mkShell {
           name = "proost-dev";
-          packages = [ rust.default rust.rust-analyzer ];
+          packages = [ (rust.default.override { extensions = [ "rust-src" "rust-analyzer" ]; }) ];
         };
       });
 }
-