diff --git a/manifest-integration-jobs.yml.jinja2 b/manifest-integration-jobs.yml.jinja2
index 5ea8b0034c533da5bc5568a4cebff90e100487e2..7f8e3ee60c46983f506f8e992eaf18da715e3187 100644
--- a/manifest-integration-jobs.yml.jinja2
+++ b/manifest-integration-jobs.yml.jinja2
@@ -15,13 +15,14 @@ workflow:
     - if: $CI_PIPELINE_SOURCE == "parent_pipeline"
 
 stages:
-  - infrastructure
+  - manifest-integration-jobs
 
 # --------------------------------------------------------------------------------------
-# Stage: infrastructure
+# Merge request pipeline
 # --------------------------------------------------------------------------------------
 integrate:
   extends: .infrastructure
+  stage: manifest-integration-jobs
   rules:
     - if: $CI_MERGE_REQUEST_IID
   cache:
@@ -47,37 +48,8 @@ integrate:
     paths:
       - manifest_revision
 
-yamllint:
-  extends: .yamllint
-
-# --------------------------------------------------------------------------------------
-# Stage: merge
-# --------------------------------------------------------------------------------------
-merge:
-  extends: .infrastructure
-  rules:
-    - if: $CI_COMMIT_BRANCH == $SOURCE_BRANCH
-  script:
-    - cd ${CI_PROJECT_DIR}
-    - .gitlab-ci/scripts/merge_into_manifest.py
-        --gitlab-url=${CI_SERVER_URL}
-        --token=${GITBOT_TOKEN}
-        --manifest-project=${TARGET_PROJECT}
-        --manifest-branch=${TARGET_BRANCH}
-        --project=${CI_PROJECT_PATH}
-        --project-branch=${SOURCE_BRANCH}
-        --commit=${CI_COMMIT_SHA}
-        --save-revision-to=manifest_revision
-        --recipe-name=${BB_RECIPE_NAME}
-  artifacts:
-    paths:
-      - manifest_revision
-
-# --------------------------------------------------------------------------------------
-# Stage: build
-# --------------------------------------------------------------------------------------
 build:
-  stage: infrastructure
+  stage: manifest-integration-jobs
   needs: ["integrate"]
   rules:
     # Do not run build if the "skip build" label is set on the merge request
@@ -91,11 +63,9 @@ build:
     branch: "integrate/${CI_PROJECT_NAME}/${CI_COMMIT_REF_NAME}/into/${TARGET_BRANCH}"
     strategy: depend
 
-# --------------------------------------------------------------------------------------
-# Stage: check
-# --------------------------------------------------------------------------------------
 check:
   extends: .infrastructure
+  stage: manifest-integration-jobs
   rules:
     # Do not run check if the "skip build" label is set on the merge request
     - if: $CI_MERGE_REQUEST_LABELS =~ /skip build/
@@ -128,3 +98,27 @@ check:
         --project=${CI_PROJECT_PATH}
         --merge-request=${MERGE_REQUEST}
         ${PARENT_MR}
+
+# --------------------------------------------------------------------------------------
+# Master pipeline
+# --------------------------------------------------------------------------------------
+merge:
+  extends: .infrastructure
+  stage: manifest-integration-jobs
+  rules:
+    - if: $CI_COMMIT_BRANCH == $SOURCE_BRANCH
+  script:
+    - cd ${CI_PROJECT_DIR}
+    - .gitlab-ci/scripts/merge_into_manifest.py
+        --gitlab-url=${CI_SERVER_URL}
+        --token=${GITBOT_TOKEN}
+        --manifest-project=${TARGET_PROJECT}
+        --manifest-branch=${TARGET_BRANCH}
+        --project=${CI_PROJECT_PATH}
+        --project-branch=${SOURCE_BRANCH}
+        --commit=${CI_COMMIT_SHA}
+        --save-revision-to=manifest_revision
+        --recipe-name=${BB_RECIPE_NAME}
+  artifacts:
+    paths:
+      - manifest_revision
diff --git a/manifest-integration-pipelines.yml.jinja2 b/manifest-integration-pipelines.yml.jinja2
index 419083abf423b3284a3132e70d1898bc28f97f78..6eb9ee6672ea6042075516b1ca3b6f47d227a2cc 100644
--- a/manifest-integration-pipelines.yml.jinja2
+++ b/manifest-integration-pipelines.yml.jinja2
@@ -12,7 +12,7 @@ workflow:
     - if: $CI_PIPELINE_SOURCE == "parent_pipeline"
 
 stages:
-  - infrastructure
+  - manifest-integration-pipelines
 
 # --------------------------------------------------------------------------------------
 # Generate job
@@ -29,6 +29,7 @@ stages:
 generate:
   extends:
     - .infrastructure
+  stage: manifest-integration-pipelines
   script:
     # The job generation script implicitly passes the OS environment to the template, so
     # that the template has access to all GitLab CI variables. Hence there is no need
@@ -59,7 +60,7 @@ generate:
       or SOURCE_BRANCH == CI_COMMIT_REF_NAME %}
 
 {{ TARGET_PROJECT }}:{{ TARGET_BRANCH }}:
-  stage: infrastructure
+  stage: manifest-integration-pipelines
   needs:
     - generate
   variables:
diff --git a/manifest-integration.yml b/manifest-integration.yml
index 81ee6fa9fe3a73317632ff058857ee06668c7f57..1ac5ea9c75f5ad82e706fa56f4225c76aa22cc68 100644
--- a/manifest-integration.yml
+++ b/manifest-integration.yml
@@ -6,7 +6,7 @@ include:
   - local: common.yml
 
 stages:
-  - infrastructure
+  - manifest-integration
 
 variables:
   MANIFEST_FILE: "default.xml"
@@ -50,18 +50,18 @@ workflow:
     # - if: $INTEGRATION =~ /^$CI_COMMIT_BRANCH:/m
     - if: $CI_COMMIT_BRANCH == $MASTER_BRANCH
 
-
-# --------------------------------------------------------------------------------------
-# Stage: infrastructure
-# --------------------------------------------------------------------------------------
 .skip-for-gitlab-ci-integrations:
   rules:
     - if: $CI_COMMIT_REF_NAME !~ /^integrate\/gitlab-ci\/.*/
 
+# --------------------------------------------------------------------------------------
+# Manifest integration jobs
+# --------------------------------------------------------------------------------------
 generate-pipelines:
   extends:
     - .infrastructure
     - .skip-for-gitlab-ci-integrations
+  stage: manifest-integration
   script:
     # The job generation script implicitly passes the OS environment to the template, so
     # that the template has access to all GitLab CI variables. Hence there is no need
@@ -77,7 +77,7 @@ generate-pipelines:
 trigger-pipelines:
   extends:
     - .skip-for-gitlab-ci-integrations
-  stage: infrastructure
+  stage: manifest-integration
   needs:
     - generate-pipelines
   trigger:
@@ -87,5 +87,5 @@ trigger-pipelines:
     strategy: depend
 
 yamllint:
-  extends:
-    - .yamllint
+  extends: .yamllint
+  stage: manifest-integration
diff --git a/manifest-pipeline.yml b/manifest-pipeline.yml
index 983a10c605050ee5a5cafc028f7cbbf3993b0aef..80a36055b7ee3260392ee74869233c3df9a2c465 100644
--- a/manifest-pipeline.yml
+++ b/manifest-pipeline.yml
@@ -6,8 +6,8 @@ include:
   - local: common.yml
 
 stages:
+  - manifest-pipeline
   - retrigger
-  - infrastructure
   - build
 
 workflow:
@@ -37,6 +37,7 @@ workflow:
     - if: $CI_PIPELINE_SOURCE == "api"
     - if: $CI_PIPELINE_SOURCE == "pipeline"
     - if: $CI_PIPELINE_SOURCE == "web"
+  stage: manifest-pipeline
 
 .short_master_pipeline:
   rules:
@@ -52,23 +53,7 @@ workflow:
     - if: $CI_COMMIT_REF_NAME == $MASTER_BRANCH
 
 # --------------------------------------------------------------------------------------
-# Stage: retrigger
-# --------------------------------------------------------------------------------------
-retrigger:
-  extends:
-    - .infrastructure
-    - .short_master_pipeline
-  stage: retrigger
-  script:
-    - .gitlab-ci/scripts/retrigger_integrating_projects.py
-        --gitlab-url=${CI_SERVER_URL}
-        --token=${GITBOT_TOKEN}
-        --manifest-project=${CI_PROJECT_PATH}
-        --manifest-branch=${MASTER_BRANCH}
-        --group=${RETRIGGER_GROUP}
-
-# --------------------------------------------------------------------------------------
-# Stage: infrastructure
+# Full build pipeline (runs in merge requests, and on master if manually triggered)
 # --------------------------------------------------------------------------------------
 generate-build-jobs:
   extends:
@@ -86,10 +71,9 @@ generate-build-jobs:
     paths:
       - build-jobs.yml
 
-trigger-build-jobs:
+build-jobs:
   extends:
     - .full_build_pipeline
-  stage: infrastructure
   needs: ["generate-build-jobs"]
   trigger:
     include:
@@ -103,8 +87,21 @@ yamllint:
     - .full_build_pipeline
 
 # --------------------------------------------------------------------------------------
-# Stage: build
+# Short master pipeline (runs on master after merging a merge request)
 # --------------------------------------------------------------------------------------
+retrigger:
+  extends:
+    - .infrastructure
+    - .short_master_pipeline
+  stage: retrigger
+  script:
+    - .gitlab-ci/scripts/retrigger_integrating_projects.py
+        --gitlab-url=${CI_SERVER_URL}
+        --token=${GITBOT_TOKEN}
+        --manifest-project=${CI_PROJECT_PATH}
+        --manifest-branch=${MASTER_BRANCH}
+        --group=${RETRIGGER_GROUP}
+
 build:merge_request:
   extends:
     - .infrastructure