diff --git a/.ddev/config.yaml b/.ddev/config.yaml
index d7a5a726d1b1894cf863e3381a28983d76311d03..fc9e914cbe45a695330f5143192670bdc26e6fa9 100644
--- a/.ddev/config.yaml
+++ b/.ddev/config.yaml
@@ -15,10 +15,9 @@ corepack_enable: false
 ddev_version_constraint: '>= 1.24.0'
 hooks:
   post-start:
+    # If needed, regenerate `composer.json` and install dependencies.
     # @see homeadditions/bin/generate-composer-json
-    - exec: 'generate-composer-json > composer.json'
-    # Install all dependencies.
-    - composer: 'install'
+    - exec: 'test -d vendor || (generate-composer-json > composer.json && composer install)'
     # The installer is part of the project template, so symlink it into the web root.
     # It needs to be a relative symlink, or it will break Package Manager.
     - exec: 'ln -s -f $(realpath -s --relative-to=$DDEV_DOCROOT/profiles project_template/$DDEV_DOCROOT/profiles/drupal_cms_installer) $DDEV_DOCROOT/profiles'
@@ -28,13 +27,16 @@ hooks:
     # https://docs.cypress.io/guides/getting-started/installing-cypress.
     - exec: 'test -d node_modules || npm clean-install --foreground-scripts'
 web_environment:
+  # If CANARY is set, Composer will install dev branches of all dependencies (be sure to
+  # run `ddev rebuild` if you change this).
+  # @see homeadditions/bin/generate-composer-json
+  # - CANARY=1
   # For faster performance, don't audit dependencies automatically.
   - COMPOSER_NO_AUDIT=1
-  # To display its UI, Cypress needs to be able to talk to an X11 server
-  # running on the host machine.
+  # To display its UI, Cypress needs to be able to talk to an X11 server running on the
+  # host machine.
   # - DISPLAY=host.docker.internal:0
-  # Download Cypress to a directory that won't be blown away every time the
-  # project is restarted.
+  # Download Cypress where it won't be deleted every time the project is restarted.
   - CYPRESS_CACHE_FOLDER=/var/www/html/.cache/cypress
   # Use the DDEV-provided database to run PHPUnit tests.
   - SIMPLETEST_DB=$DDEV_DATABASE_FAMILY://db:db@db/db
diff --git a/.ddev/homeadditions/bin/generate-composer-json b/.ddev/homeadditions/bin/generate-composer-json
index 8eea04c4ea49638fd532eb9254699df57918dc02..8762732db5683a28dc66a780729d90f0fe194b9f 100755
--- a/.ddev/homeadditions/bin/generate-composer-json
+++ b/.ddev/homeadditions/bin/generate-composer-json
@@ -24,6 +24,12 @@ if (getenv('CI')) {
   });
 }
 
+// If we're in "canary" mode, allow dev versions of all dependencies.
+if (getenv('CANARY')) {
+  $data['minimum-stability'] = 'dev';
+  $data['prefer-stable'] = FALSE;
+}
+
 // Make packages.drupal.org the lowest-priority repository, which will force the
 // components' local path repositories to take precedence.
 $repository = $data['repositories']['drupal'];
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0be25ad88d96dda9906f001cce39b48306fae590..41078d3e70ac38ed51b9d48dae0c98ba4b73e033 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -111,6 +111,8 @@ build test project:
     - .ddev/homeadditions/bin/generate-composer-json > $BUILD_DIR/composer.json
     # Install dependencies.
     - composer install --working-dir=$BUILD_DIR
+    # Remove all `.git` directories in the built project.
+    - find $BUILD_DIR -depth -type d -name '.git' -exec rm -r -f {} ';'
   artifacts:
     paths:
       - $BUILD_DIR
diff --git a/package.json b/package.json
index a26829aee49304287ec89c07c72df39a13bc04b3..737407f2dad44922d3d709b9992bbdb27251c4fa 100644
--- a/package.json
+++ b/package.json
@@ -13,5 +13,6 @@
     "@testing-library/cypress": "^10.0.2",
     "chai-string": "^1.5.0",
     "cypress": "^13.15.0"
-  }
+  },
+  "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
 }