diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index bfc4e4f7316b3b214e49aef6821fc73db2e8daab..c255d6d87fcb5853792e46b3e7233062e416fb9d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -53,6 +53,8 @@ workflow:
 variables:
   RAILS_ENV: "test"
   NODE_ENV: "test"
+  BUNDLE_WITHOUT: "production:development"
+  BUNDLE_INSTALL_FLAGS: "--jobs=$(nproc) --retry=3 --quiet"
   # we override the max_old_space_size to prevent OOM errors
   NODE_OPTIONS: --max_old_space_size=3584
   SIMPLECOV: "true"
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index 788b482f0a608bf602ed993892fdc8d4c15c2000..5097fd28eeb2c7624b1f9701eeee920a656bf7df 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -4,11 +4,13 @@
     - .qa-cache
   stage: test
   needs: []
+  variables:
+    USE_BUNDLE_INSTALL: "false"
+    SETUP_DB: "false"
   before_script:
-    - '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/ qa/spec/ee/ qa/qa/specs/features/ee/ qa/qa/ee/ qa/qa/ee.rb'
+    - !reference [.default-before_script, before_script]
     - cd qa/
-    - bundle install --clean --jobs=$(nproc) --path=vendor --retry=3 --without=development --quiet
-    - bundle check
+    - bundle_install_script
 
 qa:internal:
   extends:
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index f534f3418414b95a63782573d9f8ce7eb0c4e49d..834ba040501c2f7827525e5e4fc2c146506a9b26 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -8,7 +8,8 @@
 
 .minimal-bundle-install:
   script:
-    - run_timed_command "bundle install --jobs=$(nproc) --path=vendor --retry=3 --quiet --without default development test production puma unicorn kerberos metrics omnibus ed25519"
+    - export BUNDLE_WITHOUT="${BUNDLE_WITHOUT}:default:test:puma:unicorn:kerberos:metrics:omnibus:ed25519"
+    - bundle_install_script
 
 .base-script:
   script:
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index 78305a651d138dafceed11e3257267081693b75b..f87a57598362e6078bf3b417931026a097eed220 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -218,8 +218,8 @@ danger-review:
   stage: test
   needs: []
   before_script:
-    - source ./scripts/utils.sh
-    - run_timed_command "bundle install --jobs=$(nproc) --path=vendor --retry=3 --quiet --with danger"
+    - source scripts/utils.sh
+    - bundle_install_script "--with danger"
     - run_timed_command "retry yarn install --frozen-lockfile"
   script:
     - >
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index cd41aa0ff1477bef92408ddf1149f16027f6dde0..5753a0af4f8a4b9af97ef59f72de2d2d911447ad 100644
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -2,17 +2,9 @@
 
 export SETUP_DB=${SETUP_DB:-true}
 export USE_BUNDLE_INSTALL=${USE_BUNDLE_INSTALL:-true}
-export BUNDLE_INSTALL_FLAGS=${BUNDLE_INSTALL_FLAGS:-"--without=production development --jobs=$(nproc) --path=vendor --retry=3 --quiet"}
 
 if [ "$USE_BUNDLE_INSTALL" != "false" ]; then
-  bundle --version
-  bundle config set clean 'true'
-  run_timed_command "bundle install ${BUNDLE_INSTALL_FLAGS}"
-  run_timed_command "bundle check"
-  # When we test multiple versions of PG in the same pipeline, we have a single `setup-test-env`
-  # job but the `pg` gem needs to be rebuilt since it includes extensions (https://guides.rubygems.org/gems-with-extensions).
-  # Uncomment the following line if multiple versions of PG are tested in the same pipeline.
-  run_timed_command "bundle pristine pg"
+  bundle_install_script
 fi
 
 cp config/gitlab.yml.example config/gitlab.yml
diff --git a/scripts/utils.sh b/scripts/utils.sh
index 2e9839e4df87def4c3491b7e97fb4c06cdde1f32..d4436e1171da426fcfa9fbd3768e27d752a0e8de 100644
--- a/scripts/utils.sh
+++ b/scripts/utils.sh
@@ -13,6 +13,32 @@ function retry() {
   return 1
 }
 
+function bundle_install_script() {
+  local extra_install_args="${1}"
+
+  if [[ "${extra_install_args}" =~ "--without" ]]; then
+    echoerr "The '--without' flag shouldn't be passed as it would replace the default \${BUNDLE_WITHOUT} (currently set to '${BUNDLE_WITHOUT}')."
+    echoerr "Set the 'BUNDLE_WITHOUT' variable instead, e.g. '- export BUNDLE_WITHOUT=\"\${BUNDLE_WITHOUT}:any:other:group:not:to:install\"'."
+    exit 1;
+  fi;
+
+  bundle --version
+  bundle config set path 'vendor'
+  bundle config set clean 'true'
+
+  echo $BUNDLE_WITHOUT
+  bundle config
+
+  run_timed_command "bundle install ${BUNDLE_INSTALL_FLAGS} ${extra_install_args} && bundle check"
+
+  if [[ $(bundle info pg) ]]; then
+    # When we test multiple versions of PG in the same pipeline, we have a single `setup-test-env`
+    # job but the `pg` gem needs to be rebuilt since it includes extensions (https://guides.rubygems.org/gems-with-extensions).
+    # Uncomment the following line if multiple versions of PG are tested in the same pipeline.
+    run_timed_command "bundle pristine pg"
+  fi
+}
+
 function setup_db_user_only() {
   source scripts/create_postgres_user.sh
 }