diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index ca8d81737b84836b6034fe9d8bde40f6fdf7d1d0..b4a3232fcf7cc4c56409ae688e995e2cf4c5f090 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -509,4 +509,4 @@
       url="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/repository/files/scripts%2Futils.sh/raw?ref=${CI_COMMIT_SHA}"
       curl -f --header "Private-Token: ${PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE}" "${url}" --create-dirs --output scripts/utils.sh
     - source scripts/utils.sh
-    - download_files ${FILES_TO_DOWNLOAD}
+    - run_timed_command "download_files ${FILES_TO_DOWNLOAD}"
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index 2844669a89d7b8e5370965ea1a8682cad90c85ba..5b654627465b31e1b61c148b41bcfc09125aca15 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -447,6 +447,7 @@ rspec:artifact-collector ee:
 rspec:coverage:
   extends:
     - .coverage-base
+    - .fast-no-clone-job
     - .rails:rules:rspec-coverage
   stage: post-test
   needs:
@@ -477,7 +478,20 @@ rspec:coverage:
     # Memory jobs
     - job: memory-on-boot
       optional: true
+  variables:
+    FILES_TO_DOWNLOAD: >
+      config/bundler_setup.rb
+      Gemfile
+      Gemfile.lock
+      scripts/merge-simplecov
+      spec/simplecov_env_core.rb
+      spec/simplecov_env.rb
+  before_script:
+    - !reference [".fast-no-clone-job", before_script]
+    - run_timed_command "download_local_gems"
+    - !reference [".coverage-base", before_script]
   script:
+    - chmod u+x scripts/merge-simplecov # Not the right permissions when downloading the script via the API.
     - run_timed_command "bundle exec scripts/merge-simplecov"
   coverage: '/LOC \((\d+\.\d+%)\) covered.$/'
   artifacts:
diff --git a/doc/development/pipelines/internals.md b/doc/development/pipelines/internals.md
index 97424e02437e0b59142d6bc38594277d9769c507..904ac11511d3768a898f1860f2853158e4668a46 100644
--- a/doc/development/pipelines/internals.md
+++ b/doc/development/pipelines/internals.md
@@ -368,6 +368,8 @@ my-job:
 
 - This pattern does not work if a script relies on `git` to access the repository, because we don't have the repository without cloning or fetching.
 - The job using this pattern needs to have `curl` available.
+- If you need to run `bundle install` in the job (even using `BUNDLE_ONLY`), you need to download the gems that are stored in the `gitlab-org/gitlab` project.
+  - You can use the `download_local_gems` shell command for that purpose.
 
 #### Where is this pattern used?
 
@@ -387,5 +389,12 @@ my-job:
     - `scripts/review_apps/review-apps.sh`
     - `scripts/review_apps/seed-dast-test-data.sh`
     - `VERSION`
+  - `rspec:coverage` for:
+    - `config/bundler_setup.rb`
+    - `Gemfile.lock`
+    - `Gemfile`
+    - `scripts/merge-simplecov`
+    - `spec/simplecov_env_core.rb`
+    - `spec/simplecov_env.rb`
 
 Additionally, `scripts/utils.sh` is always downloaded from the API when this pattern is used (this file contains the code for `.fast-no-clone-job`).
diff --git a/scripts/utils.sh b/scripts/utils.sh
index 4ed56b2de1a82e1a13cfaac4d7fc2aa72f88060c..e19622d07c6b157a7f9e022d896518193fc10121 100644
--- a/scripts/utils.sh
+++ b/scripts/utils.sh
@@ -416,3 +416,38 @@ function url_encode() {
     -e 's/}/%7d/g' \
     -e 's/~/%7e/g'
 }
+
+# Download the local gems in `gems` and `vendor/gems` folders from the API.
+#
+# This is useful if you need to run bundle install while not doing a git clone of the gitlab-org/gitlab repo.
+function download_local_gems() {
+  for folder_path in vendor/gems gems; do
+    local output="${folder_path}.tar.gz"
+
+    # From https://docs.gitlab.com/ee/api/repositories.html#get-file-archive:
+    #
+    #   This endpoint can be accessed without authentication if the repository is publicly accessible.
+    #   For GitLab.com users, this endpoint has a rate limit threshold of 5 requests per minute.
+    #
+    # We don't want to set a token for public repo (e.g. gitlab-org/gitlab), as 5 requests/minute can
+    # potentially be reached with many pipelines running in parallel.
+    local private_token_header=""
+    if [[ "${CI_PROJECT_VISIBILITY}" != "public" ]]; then
+      private_token_header="Private-Token: ${PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE}"
+    fi
+
+    echo "Downloading ${folder_path}"
+
+    url=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/repository/archive
+    curl -f \
+      --get \
+      --header "${private_token_header}" \
+      --output "${output}" \
+      --data-urlencode "sha=${CI_COMMIT_SHA}" \
+      --data-urlencode "path=${folder_path}" \
+      "${url}"
+
+    tar -zxf "${output}" --strip-component 1
+    rm "${output}"
+  done
+}