diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1c63888c015a9ca7d4b24cf94af2d164b0e5e90f..76dce30e2e5137a8a9199f2d739f96db92988406 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -73,41 +73,8 @@ system-tests:
 # A template for a job that can update one of the grids.
 .update-grid: &UPDATE_GRID
   stage: "deploy"
-  script:
-    # Announce our intentions.
-    - |
-      echo "Hello $GITLAB_USER_LOGIN from $CI_JOB_NAME. I was triggered by $CI_PIPELINE_SOURCE"
-      echo "and I am deploying the $CI_COMMIT_BRANCH branch to the $CI_ENVIRONMENT_NAME environment."
-
-    # Copy the deploy key from the environment to a file so we can actually
-    # tell ssh to use it.
-    - |
-      # Make sure the deploy key file is not readable by anyone else.  Not
-      # that there should be anyone else looking - but OpenSSH won't even read
-      # it if it looks like it is too open.
-      umask 077
-
-      # Make up a safe-ish place on the filesystem to write the key.
-      KEY_PATH="$(mktemp -d)/deploy_key"
-
-      # The environment variable holding the path to the key is configured
-      # with GitLab using Terraform so we can retain some bare minimum level
-      # of confidentiality.
-      #
-      # It contains the *path to the key*.  It does not contain the key
-      # itself.
-      base64 --decode "${PRIVATESTORAGEIO_SSH_DEPLOY_KEY_PATH}" > "${KEY_PATH}"
-
-    # Update the deployment
-    - |
-      ./ci-tools/update-grid-servers "${KEY_PATH}" "${CI_ENVIRONMENT_NAME}"
-
-    # Remove the key from the filesystem to reduce the chance of unintentional
-    # disclosure.  Overall our handling of this key is still not *particulary*
-    # safe or secure but that's why the key is only authorized to perform a
-    # single very specific operation.
-    - |
-      rm -v "${KEY_PATH}"
+  script: |
+    env --ignore-environment - NIX_PATH=$NIX_PATH GITLAB_USER_LOGIN=$GITLAB_USER_LOGIN CI_JOB_NAME=$CI_JOB_NAME CI_PIPELINE_SOURCE=$CI_PIPELINE_SOURCE CI_COMMIT_BRANCH=$CI_COMMIT_BRANCH ./ci-tools/update-grid-servers "${PRIVATESTORAGEIO_SSH_DEPLOY_KEY_PATH}" "${CI_ENVIRONMENT_NAME}"
 
 # Update the staging deployment - only on a merge to the staging branch.
 update-staging:
diff --git a/ci-tools/update-grid-servers b/ci-tools/update-grid-servers
index 0d0195262a8ef25e28d7f5c0d5b4ca05439c002f..c5206fd481096cc8eddb2a2c313cff7d92fe2db6 100755
--- a/ci-tools/update-grid-servers
+++ b/ci-tools/update-grid-servers
@@ -1,5 +1,5 @@
 #!/usr/bin/env nix-shell
-#!nix-shell -i bash -p jp
+#!nix-shell -i bash -p jp nix openssh
 
 #
 # Tell all servers belonging to a certain grid that they should update
@@ -13,8 +13,10 @@ set -euxo pipefail
 HERE=$(dirname $0)
 
 # Get the path to the ssh key which authorizes us to deliver this
-# notification.
-DEPLOY_KEY=$1
+# notification.  This path contains a base64-encoded key because of
+# limitations placed on the values of GitLab job environment variables.  We'll
+# decode it later.
+ENCODED_DEPLOY_KEY_PATH=$1
 shift
 
 # Get the name of the grid to which we're going to deliver notification.  This
@@ -28,7 +30,7 @@ update_one_node() {
     grid_name=$1
     shift
 
-    deploy_key=$1
+    deploy_key_path=$1
     shift
 
     node=$1
@@ -42,12 +44,12 @@ update_one_node() {
     # and trigger the update on the host.  There's no command here because the
     # deployment key is restricted *only* the deloyment update command and the
     # ssh server will supply that command itself.
-    ssh -o "UserKnownHostsFile=${HERE}/known_hosts.${grid_name}" -i "${deploy_key}" "deployment@${node}"
+    ssh -o "UserKnownHostsFile=${HERE}/known_hosts.${grid_name}" -i "${deploy_key_path}" "deployment@${node}"
 }
 
 # Tell all servers belonging to one grid to update themselves.
 update_grid_nodes() {
-    deploy_key=$1
+    deploy_key_path=$1
     shift
 
     gridname=$1
@@ -80,8 +82,51 @@ update_grid_nodes() {
 	    # This isn't a server, it's part of the morph configuration.
 	    continue
 	fi
-	update_one_node "${gridname}" "${deploy_key}" "${node}.${domain}"
+	update_one_node "${gridname}" "${deploy_key_path}" "${node}.${domain}"
     done
 }
 
-update_grid_nodes "${DEPLOY_KEY}" "${GRIDNAME}"
+decode_deploy_key() {
+    encoded_key_path=$1
+    shift
+
+    # Make sure the deploy key file is not readable by anyone else.  Not
+    # that there should be anyone else looking - but OpenSSH won't even read
+    # it if it looks like it is too open.
+    umask 077
+
+    # Make up a safe-ish place on the filesystem to write the key.
+    decoded_key_path="$(mktemp -d)/deploy_key"
+
+    # Decode the contents of the encoded key path into a decoded key path.
+    base64 --decode "${encoded_key_path}" > "${decoded_key_path}"
+
+    # If OpenSSH doesn't find a newline after the last line of the key
+    # material then it fails to parse it.  So, make sure there is one.  If
+    # there was already one, it's fine to have an extra.
+    echo >> "${decoded_key_path}"
+
+    # Remove the key from the filesystem to reduce the chance of unintentional
+    # disclosure.  Overall our handling of this key is still not *particulary*
+    # safe or secure but that's why the key is only authorized to perform a
+    # single very specific operation.
+    rm -v "${encoded_key_path}" >/dev/stderr
+
+    echo -n "${decoded_key_path}"
+}
+
+# Announce our intentions.
+show_banner() {
+    echo "Hello $GITLAB_USER_LOGIN from $CI_JOB_NAME. I was triggered by $CI_PIPELINE_SOURCE"
+    echo "and I am deploying the $CI_COMMIT_BRANCH branch to the $GRIDNAME environment."
+}
+
+show_banner
+
+DEPLOY_KEY_PATH="$(decode_deploy_key "${ENCODED_DEPLOY_KEY_PATH}")"
+
+# Update the deployment
+update_grid_nodes "${DEPLOY_KEY_PATH}" "${GRIDNAME}"
+
+# Remove the decoded key from the filesystem as well.
+rm -v "${DEPLOY_KEY_PATH}"