diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 14d8e4b46fc8339263d4f0111cdbf5df42749a02..210486aceb10d7a7c4eb7c98a3ddd0afde89b70a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -18,6 +18,10 @@
 
     - when: "never"
 
+stages:
+  - "build"
+  - "deploy"
+
 default:
   # Guide the choice of an appropriate runner for all these jobs.
   # https://docs.gitlab.com/ee/ci/runners/#runner-runs-only-tagged-jobs
@@ -31,6 +35,7 @@ variables:
 
 docs:
   <<: *RUN_ON_MERGE_REQUEST
+  stage: "build"
   script:
     - "nix-build --attr docs --out-link result-docs"
     # GitLab wants to lchown artifacts.  It can't do that to store paths.  Get
@@ -43,14 +48,14 @@ docs:
 
 unit-tests:
   <<: *RUN_ON_MERGE_REQUEST
+  stage: "build"
   script:
     - "nix-build --attr unit-tests && cat result"
 
 .morph-build: &MORPH_BUILD
   <<: *RUN_ON_MERGE_REQUEST
-
   timeout: "3 hours"
-
+  stage: "build"
   script:
     - |
       # GRID is set in one of the "instantiations" of this job template.
@@ -82,6 +87,7 @@ morph-build-production:
 
 vulnerability-scan:
   <<: *RUN_ON_MERGE_REQUEST
+  stage: "build"
   script:
     - "ci-tools/vulnerability-scan security-report.json"
     - "ci-tools/count-vulnerabilities <security-report.json"
@@ -94,11 +100,13 @@ vulnerability-scan:
 system-tests:
   <<: *RUN_ON_MERGE_REQUEST
   timeout: "3 hours"
+  stage: "build"
   script:
     - "nix-build --attr system-tests"
 
 # A template for a job that can update one of the grids.
 .update-grid: &UPDATE_GRID
+  stage: "deploy"
   script: |
     env --ignore-environment - \
       NIX_PATH="$NIX_PATH" \
@@ -112,12 +120,6 @@ system-tests:
 update-staging:
   <<: *UPDATE_GRID
 
-  # https://docs.gitlab.com/ee/ci/yaml/index.html#needs
-  needs:
-    # Only deploy if the code looks good.
-    - "system-tests"
-    - "morph-build-staging"
-
   # https://docs.gitlab.com/ee/ci/yaml/#rules
   rules:
     # https://docs.gitlab.com/ee/ci/yaml/index.html#rulesif
@@ -141,12 +143,6 @@ update-staging:
 deploy-to-production:
   <<: *UPDATE_GRID
 
-  # https://docs.gitlab.com/ee/ci/yaml/index.html#needs
-  needs:
-    # Only deploy if the code looks good.
-    - "system-tests"
-    - "morph-build-production"
-
   # https://docs.gitlab.com/ee/ci/yaml/#rules
   rules:
     # https://docs.gitlab.com/ee/ci/yaml/index.html#rulesif
@@ -161,6 +157,7 @@ deploy-to-production:
 
 update-nixpkgs:
   <<: *RUN_ON_SCHEDULE
+  stage: "build"
   script:
     - |
       ./ci-tools/with-ssh-agent \