diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6e7abb71b10c5736cc75fe7953e2d908d073f998..e88c625fd80ec7de203f736942946347bcd450f6 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,16 +1,14 @@
 default:
-  image: python:3.7-buster # minimum supported version
+  image: $CI_REGISTRY/lofar2.0/lofar-station-client/ci_python37:$CI_COMMIT_SHORT_SHA # minimum supported version
   # Make sure each step is executed in a virtual environment with some basic dependencies present
   before_script:
-    - apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y libboost-python-dev libtango-dev # Needed to install pytango
     - python --version # For debugging
-    - python -m pip install --upgrade pip
-    - pip install --upgrade tox
   cache:
     paths:
       - .cache/pip
 
 stages:
+  - image
   - lint
   - test
   - package
@@ -21,6 +19,19 @@ stages:
 variables:
   PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
 
+build_test_image_python37:
+  stage: image
+  image: docker
+  services:
+    - name: docker:dind
+  variables:
+    DOCKER_TLS_CERTDIR: "/certs"
+  before_script:
+    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
+  script:
+    - docker build -t $CI_REGISTRY/lofar2.0/lofar-station-client/ci_python37:$CI_COMMIT_SHORT_SHA -f docker/Dockerfile.ci_python37 docker
+    - docker push $CI_REGISTRY/lofar2.0/lofar-station-client/ci_python37:$CI_COMMIT_SHORT_SHA
+
 run_black:
   stage: lint
   script:
@@ -47,23 +58,33 @@ run_unit_tests_py37:
     - echo "run python3.7 unit tests /w coverage"
     - tox -e py37
 
+.run_unit_tests_pyXX:
+  # installs the prerequisites explicitly, instead of piggy backing
+  # on the ci_python37 image. This allows us to use different base
+  # images with different python versions.
+  stage: test
+  before_script:
+    - apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y libboost-python-dev libtango-dev # Needed to install pytango
+    - python -m pip install --upgrade pip
+    - pip install --upgrade tox
+
 run_unit_tests_py38:
+  extends: .run_unit_tests_pyXX
   image: python:3.8-buster
-  stage: test
   script:
     - echo "run python3.8 unit tests /w coverage"
     - tox -e py38
 
 run_unit_tests_py39:
+  extends: .run_unit_tests_pyXX
   image: python:3.9-bullseye
-  stage: test
   script:
     - echo "run python3.9 unit tests /w coverage"
     - tox -e py39
 
 run_unit_tests_py310:
+  extends: .run_unit_tests_pyXX
   image: python:3.10-bullseye
-  stage: test
   script:
     # Debian Bullseye ships with libboost-python linked to Python 3.9. Use the one from Debian Sid instead.
     - echo 'deb http://deb.debian.org/debian sid main' >> /etc/apt/sources.list
diff --git a/docker/Dockerfile.ci_python37 b/docker/Dockerfile.ci_python37
new file mode 100644
index 0000000000000000000000000000000000000000..1f4362fcff091ce1ba35661777eeb3e78c2a8b98
--- /dev/null
+++ b/docker/Dockerfile.ci_python37
@@ -0,0 +1,9 @@
+FROM python:3.7-buster
+
+# Install PyTango dependencies
+RUN apt-get update -y
+RUN DEBIAN_FRONTEND=noninteractive apt-get install -y libboost-python-dev libtango-dev
+
+# Make sure we have the latest tooling for our tests
+RUN python -m pip install --upgrade pip
+RUN pip install --upgrade tox