diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 37e7e5731f74baad5feac86cd4ec5de3eebc4fb4..d30512df5ffdfdde3a666582fc4fe462b6c33f68 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,6 +1,8 @@
+variables:
+  CI_REGISTRY_IMAGE_ENV: $CI_REGISTRY/fdo/fdo-manager-webui/testenv:$CI_COMMIT_REF_NAME
 
 default:
-  image: node:lts-alpine
+  image: docker:22.06-rc
   tags:
     - docker
 
@@ -9,10 +11,39 @@ workflow:
     - if: $CI_PIPELINE_SOURCE != "merge_request_event" && $CI_COMMIT_REF_NAME != $CI_COMMIT_TAG
 
 stages:
+  - setup
   - test
 
-webui:build-test:
-  stage: test
+webui:setup:
+  stage: setup
+  rules:
+    - if: $RUN_SETUP_STAGE == "false"
+      when: never
+    - if: $CI_PIPELINE_SOURCE == "schedule"
+    - if: $CI_PIPELINE_SOURCE == "web"
+    - if: $CI_PIPELINE_SOURCE != "schedule"
+      changes:
+        - .test/Dockerfile
+        - package.json
+        - package-lock.json
+        - .gitlab-ci.yml
   script:
+    - echo "registry $CI_REGISTRY"
+    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
+    - docker build --pull -t $CI_REGISTRY_IMAGE_ENV -f .test/Dockerfile .
+    - docker push $CI_REGISTRY_IMAGE_ENV
+
+.webui:job: &webui-job
+  image: $CI_REGISTRY_IMAGE_ENV
+  stage: test
+  needs:
+    - job: "webui:setup"
+      optional: true
+  before_script:
+    - cp -r -t ./ /webui/node_modules
     - npm install
+
+webui:build-test:
+  <<: *webui-job
+  script:
     - npm run build
diff --git a/.test/Dockerfile b/.test/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..501edfd7b3fa7a56e16bdf1b30ab4e2739cbfe9a
--- /dev/null
+++ b/.test/Dockerfile
@@ -0,0 +1,10 @@
+FROM node:lts-alpine
+
+COPY ./package.json /webui/package.json
+COPY ./package-lock.json /webui/package-lock.json
+
+WORKDIR /webui
+
+RUN npm install
+
+ENTRYPOINT [""]