diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a8e5bd76cf5490781a26522c9aca18628d896443..d25cd77a7c979f270706c083be096fad201e7e8a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,5 +1,6 @@
 stages:
   - build
+  - test
   - release
 
 
@@ -36,6 +37,38 @@ build:iperf3:
 
 
 
+.test:
+  stage: test
+  tags: [docker]
+  image:
+    name: alpine:3
+    entrypoint: ['']
+  services:
+    - name: ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}-${BUILD_TARGET}
+      alias: iperf-container
+  before_script:
+    - apk add --no-cache iperf iperf3
+
+test:iperf2:
+  extends: .test
+  needs: ['build:iperf2']
+  variables:
+    BUILD_TARGET: iperf2
+  script:
+    - iperf -c iperf-container -p 5001 -b 10M -i 1
+    # iperf always has a truthy exit code, so we have to grep for output instead
+    - iperf -c iperf-container -p 5001 -b 10M | grep ' Mbits/sec'
+
+test:iperf3:
+  extends: .test
+  needs: ['build:iperf3']
+  variables:
+    BUILD_TARGET: iperf3
+  script:
+    - iperf3 -c iperf-container -p 5201 --connect-timeout 10000 -b 10M
+
+
+
 .release:
   stage: release
   tags: [docker]
@@ -49,14 +82,14 @@ build:iperf3:
 
 release:iperf2:
   extends: .release
-  needs: ['build:iperf2']
+  needs: ['test:iperf2']
   script:
     - crane tag ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}-iperf2 2.2
     - crane tag ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}-iperf2 2
 
 release:iperf3:
   extends: .release
-  needs: ['build:iperf3']
+  needs: ['test:iperf3']
   script:
     - crane tag ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}-iperf3 3.17
     - crane tag ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}-iperf3 3