diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 50483f346677f884b46e6d380913db156765d91c..e31dbd69e70c69f6bdb1c6b0e8a8fe777c8037b9 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -27,11 +27,20 @@ default:
   # Default job timeout doesn't work: https://gitlab.com/gitlab-org/gitlab/-/issues/387528
   timeout: 90m
 
+.old-ruby-variables: &old-ruby-variables
+  RUBY_VERSION: "3.0"
+  CACHE_EDITION: "GITLAB_RUBY3_0"
+  USE_OLD_RUBY_VERSION: "true"
+
 .default-ruby-variables: &default-ruby-variables
   RUBY_VERSION: "3.1"
+  CACHE_EDITION: "GITLAB_RUBY3_1"
+  USE_OLD_RUBY_VERSION: "true"
 
 .next-ruby-variables: &next-ruby-variables
   RUBY_VERSION: "3.2"
+  CACHE_EDITION: "GITLAB_RUBY3_2"
+  USE_OLD_RUBY_VERSION: "false"
 
 .default-branch-pipeline-failure-variables: &default-branch-pipeline-failure-variables
   CREATE_RAILS_TEST_FAILURE_ISSUES: "true"
@@ -51,6 +60,7 @@ workflow:
   rules:
     - if: '$CI_PROJECT_PATH == "gitlab-org/gitaly" && $CI_PIPELINE_SOURCE == "parent_pipeline" && $GITALY_TEST'
       variables:
+        <<: *default-ruby-variables
         PIPELINE_NAME: 'Gitaly Rails Test Pipeline'
     # If `$FORCE_GITLAB_CI` is set, create a pipeline.
     - if: '$FORCE_GITLAB_CI'
@@ -64,26 +74,36 @@ workflow:
     # they serve no purpose and will run anyway when the changes are merged.
     - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^release-tools\/\d+\.\d+\.\d+-rc\d+$/ && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^[\d-]+-stable(-ee)?$/ && $CI_PROJECT_PATH == "gitlab-org/gitlab"'
       when: never
-    # For merge requests running exclusively in Ruby 3.1
+    - if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-in-ruby3_0/'
+      variables:
+        <<: *old-ruby-variables
+        PIPELINE_NAME: 'Ruby $RUBY_VERSION $CI_MERGE_REQUEST_EVENT_TYPE MR pipeline'
+        NO_SOURCEMAPS: 'true'
     - if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-in-ruby3_1/'
       variables:
         <<: *default-ruby-variables
         PIPELINE_NAME: 'Ruby $RUBY_VERSION $CI_MERGE_REQUEST_EVENT_TYPE MR pipeline'
         NO_SOURCEMAPS: 'true'
-    - if: '$CI_MERGE_REQUEST_LABELS =~ /Community contribution/'
+    - if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-in-ruby3_2/'
       variables:
         <<: *next-ruby-variables
+        PIPELINE_NAME: 'Ruby $RUBY_VERSION $CI_MERGE_REQUEST_EVENT_TYPE MR pipeline'
+        NO_SOURCEMAPS: 'true'
+    - if: '$CI_MERGE_REQUEST_LABELS =~ /Community contribution/'
+      variables:
+        <<: *default-ruby-variables
         GITLAB_DEPENDENCY_PROXY_ADDRESS: ""
         PIPELINE_NAME: 'Ruby $RUBY_VERSION $CI_MERGE_REQUEST_EVENT_TYPE MR pipeline (community contribution)'
         NO_SOURCEMAPS: 'true'
     - <<: *if-merge-request-security-canonical-sync
       variables:
+        <<: *default-ruby-variables
         PIPELINE_NAME: '$CI_DEFAULT_BRANCH security->canonical sync'
         SKIP_MESSAGE: 'MR only contains changes from the security mirror, which have already been reviewed, tested and deployed.'
     # For (detached) merge request pipelines.
     - if: '$CI_MERGE_REQUEST_IID'
       variables:
-        <<: *next-ruby-variables
+        <<: *default-ruby-variables
         <<: *default-merge-request-slow-tests-variables
         PIPELINE_NAME: 'Ruby $RUBY_VERSION $CI_MERGE_REQUEST_EVENT_TYPE MR pipeline'
         NO_SOURCEMAPS: 'true'
@@ -93,7 +113,10 @@ workflow:
         <<: [*default-ruby-variables, *default-branch-pipeline-failure-variables]
         CRYSTALBALL: "true"
         PIPELINE_NAME: 'Scheduled Ruby $RUBY_VERSION $CI_COMMIT_BRANCH branch pipeline'
-    # Run pipelines for ruby3_2 branch
+    - if: '$CI_COMMIT_BRANCH == "ruby3_0" && $CI_PIPELINE_SOURCE == "schedule"'
+      variables:
+        <<: *old-ruby-variables
+        PIPELINE_NAME: 'Scheduled Ruby $RUBY_VERSION $CI_COMMIT_BRANCH branch pipeline'
     - if: '$CI_COMMIT_BRANCH == "ruby3_2" && $CI_PIPELINE_SOURCE == "schedule"'
       variables:
         <<: *next-ruby-variables
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index 277faa1f13f61c80fbbcd315fb1305fc9a41b278..379164e4accd290f51a9d6f063f2e7b4dfa6db4f 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -90,9 +90,9 @@ trigger-omnibus:
     TOP_UPSTREAM_SOURCE_PROJECT: $CI_PROJECT_PATH
     SECURITY_SOURCES: $SECURITY_SOURCES
     CACHE_UPDATE: $OMNIBUS_GITLAB_CACHE_UPDATE
-    USE_OLD_RUBY_VERSION: "true"
-    CACHE_EDITION: "GITLAB_RUBY3_1"
-    NEXT_RUBY_VERSION: "3.2.2"
+    USE_OLD_RUBY_VERSION: $USE_OLD_RUBY_VERSION
+    CACHE_EDITION: $CACHE_EDITION
+    NEXT_RUBY_VERSION: $FULL_RUBY_VERSION
     BUILD_ON_ALL_OS: $OMNIBUS_GITLAB_BUILD_ON_ALL_OS
     SKIP_QA_TEST: "true"
     ee: $EE
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 5c8e713512304eb1d684eed87a349dc9bae1cec1..8522a00722c22d11aa142d37f53f3757c071c84e 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -1543,10 +1543,6 @@
     - !reference [".qa:rules:package-and-test-never-run", rules]
     - <<: *if-merge-request-targeting-stable-branch
       changes: *setup-test-env-patterns
-    - <<: *if-ruby3_2-branch
-      variables:
-        USE_OLD_RUBY_VERSION: 'false'
-        CACHE_EDITION: 'GITLAB_RUBY3_2'
     - <<: *if-merge-request
       changes: *dependency-patterns
       variables:
@@ -1589,9 +1585,6 @@
       changes: *setup-test-env-patterns
       allow_failure: true
     - <<: *if-ruby3_2-branch
-      variables:
-        USE_OLD_RUBY_VERSION: 'false'
-        CACHE_EDITION: 'GITLAB_RUBY3_2'
       allow_failure: true
     - <<: *if-merge-request
       changes: *dependency-patterns
@@ -1640,9 +1633,6 @@
     - <<: *if-merge-request-targeting-stable-branch
       changes: *setup-test-env-patterns
     - <<: *if-ruby3_2-branch
-      variables:
-        USE_OLD_RUBY_VERSION: 'false'
-        CACHE_EDITION: 'GITLAB_RUBY3_2'
     - <<: *if-merge-request
       # Certain components trigger a rebuild of the e2e GDK image so we want to test it too
       changes: *gdk-component-patterns
@@ -2797,6 +2787,7 @@
 
 .setup:rules:verify-default-ruby:
   rules:
+    - <<: *if-merge-request-labels-run-in-ruby3_0
     - <<: *if-merge-request-labels-run-in-ruby3_2
 
 .setup:rules:verify-tests-yml:
diff --git a/.gitlab/ci/setup.gitlab-ci.yml b/.gitlab/ci/setup.gitlab-ci.yml
index 15f3695739248786e2536dc92e7ce086f05fec9b..a95fccf6e8fe3f221c2a121ee3afad8f2bce4d9d 100644
--- a/.gitlab/ci/setup.gitlab-ci.yml
+++ b/.gitlab/ci/setup.gitlab-ci.yml
@@ -71,8 +71,7 @@ verify-default-ruby:
     - .setup:rules:verify-default-ruby
   stage: prepare
   script:
-    - echo 'Please remove label ~"pipeline:run-in-ruby3_2" so we do test against default Ruby version before merging the merge request'
-    - echo 'This does not work yet. See https://gitlab.com/gitlab-org/gitlab/-/issues/428537'
+    - echo 'Please remove label ~"pipeline:run-in-ruby3_2" or ~"pipeline:run-in-ruby3_0" so we do test against default Ruby version before merging the merge request'
     - exit 1
 
 verify-tests-yml:
@@ -208,7 +207,7 @@ trigger-omnibus-env:
       for version_file in *_VERSION; do echo "$version_file=$(cat $version_file)" >> $BUILD_ENV; done
       echo "OMNIBUS_GITLAB_BUILD_ON_ALL_OS=${OMNIBUS_GITLAB_BUILD_ON_ALL_OS:-false}" >> $BUILD_ENV
       echo "USE_OLD_RUBY_VERSION=${USE_OLD_RUBY_VERSION:-false}" >> $BUILD_ENV
-      echo "NEXT_RUBY_VERSION=${NEXT_RUBY_VERSION}" >> $BUILD_ENV
+      ruby -e 'puts "FULL_RUBY_VERSION=#{RUBY_VERSION}"' >> $BUILD_ENV
       echo "GITLAB_ASSETS_TAG=$(assets_image_tag)" >> $BUILD_ENV
       echo "EE=$([[ $FOSS_ONLY == '1' ]] && echo 'false' || echo 'true')" >> $BUILD_ENV
       define_trigger_branch_in_build_env