diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 632eed71caa31e85f9b9b3d3fa0f0caf21216e41..ab7fd0264bfa540b8b2c0b4ad3c5f362958f89a3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -84,9 +84,6 @@ stages:
     - echo Success!
 
 fe-deploy-job:
-  variables:
-    GLIBC_URL: https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.34-r0/glibc-2.34-r0.apk
-    BUTLER_URL: https://broth.itch.ovh/butler/linux-amd64/LATEST/archive/default
   image: node:16-alpine
   stage: deploy
   cache:
@@ -96,23 +93,7 @@ fe-deploy-job:
       paths:
         - frontend/node_modules
   before_script:
-    - "#=[ install tools ]====================="
-    # NOTE: Alpine Linux stuff if linked with muslc. Butler is linked with glibc, and this causes an error -- see https://stackoverflow.com/a/66974607/6438061 for details. We need to install a compatibility layer: https://github.com/sgerrand/alpine-pkg-glibc#installing
-    - "#-[ install glibc ]---------------------"
-    - wget -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub
-    - wget -O glibc.apk $GLIBC_URL
-    - apk add glibc.apk
-    - rm glibc.apk
-    - "#-[ install unzip ]---------------------"
-    - apk add --no-cache unzip
-    - "#-[ install butler ]--------------------"
-    - wget -O butler.zip $BUTLER_URL
-    - unzip butler.zip *.so -d /usr/local/lib
-    - unzip butler.zip butler -d /usr/local/bin
-    - chmod +x /usr/local/bin/butler
-    - rm butler.zip
-    - butler -V
-    # NOTE: sometimes this fails with error `runtime/cgo: pthread_create failed: Operation not permitted` and I'm not sure why. A strict Docker's seccomp policy would explain this, but then why is it flaky?!
+    - ./ci/install-butler-on-alpine.sh
   script:
     - cd frontend
     - npm install
diff --git a/ci/install-butler-on-alpine.sh b/ci/install-butler-on-alpine.sh
new file mode 100755
index 0000000000000000000000000000000000000000..d26155d4915e6f68effb84dbe9ad68247148b524
--- /dev/null
+++ b/ci/install-butler-on-alpine.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env sh
+
+set -e # exit of first error
+#set -o xtrace # trace commands
+
+# NOTE: Alpine Linux stuff if linked with muslc. Butler is linked with glibc, and this causes an error -- see https://stackoverflow.com/a/66974607/6438061 for details. We need to install a compatibility layer: https://github.com/sgerrand/alpine-pkg-glibc#installing
+
+echo "Installing glibc..."
+
+GLIBC_VERSION=2.34-r0
+GLIBC_URL=https://github.com/sgerrand/alpine-pkg-glibc/releases/download/$GLIBC_VERSION/glibc-$GLIBC_VERSION.apk
+GLIBC_KEY=https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub
+
+wget -P /etc/apk/keys $GLIBC_KEY
+wget -O glibc.apk $GLIBC_URL
+apk add glibc.apk
+rm glibc.apk
+
+echo "Installing unzip..."
+
+apk add --no-cache unzip
+
+echo "Installing butler..."
+
+BUTLER_VERSION=LATEST
+BUTLER_URL=https://broth.itch.ovh/butler/linux-amd64/$BUTLER_VERSION/archive/default
+
+wget -O butler.zip $BUTLER_URL
+unzip butler.zip *.so -d /usr/local/lib
+unzip butler.zip butler -d /usr/local/bin
+chmod +x /usr/local/bin/butler
+rm butler.zip
+butler -V
+
+# NOTE: sometimes this fails with error `runtime/cgo: pthread_create failed: Operation not permitted` and I'm not sure why. A strict Docker's seccomp policy would explain this, but then why is it flaky?!