diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8754dafc4..15fc2279a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -158,14 +158,14 @@ jobs:
           cd src/
           pipenv --python ${{ steps.setup-python.outputs.python-version }} run pytest -ra
       -
-        name: Upload coverage to Codecov
+        name: Upload coverage
         if: ${{ matrix.python-version == env.DEFAULT_PYTHON_VERSION }}
-        uses: codecov/codecov-action@v3
+        uses: actions/upload-artifact@v3
         with:
-          # not required for public repos, but intermittently fails otherwise
-          token: ${{ secrets.CODECOV_TOKEN }}
-          # future expansion
-          flags: backend
+          name: backend-coverage-report
+          path: src/coverage.xml
+          retention-days: 7
+          if-no-files-found: warn
       -
         name: Stop containers
         if: always()
@@ -210,15 +210,7 @@ jobs:
           name: jest-coverage-report
           path: src-ui/coverage
           retention-days: 7
-      -
-        name: Upload frontend coverage to Codecov
-        if: always()
-        uses: codecov/codecov-action@v3
-        with:
-          # not required for public repos, but intermittently fails otherwise
-          token: ${{ secrets.CODECOV_TOKEN }}
-          # future expansion
-          flags: frontend
+          if-no-files-found: warn
       -
         name: Run Playwright e2e tests
         run: cd src-ui && npx playwright test
@@ -231,6 +223,45 @@ jobs:
           path: src-ui/playwright-report
           retention-days: 7
 
+  tests-coverage-upload:
+    name: "Upload coverage"
+    runs-on: ubuntu-22.04
+    needs:
+      - tests-backend
+      - tests-frontend
+    steps:
+      -
+        uses: actions/checkout@v3
+      -
+        name: Download frontend coverage
+        uses: actions/download-artifact@v3
+        with:
+          name: jest-coverage-report
+          path: src-ui/
+      -
+        name: Upload frontend coverage to Codecov
+        uses: codecov/codecov-action@v3
+        with:
+          # not required for public repos, but intermittently fails otherwise
+          token: ${{ secrets.CODECOV_TOKEN }}
+          flags: frontend
+          directory: src-ui/
+      -
+        name: Download backend coverage
+        uses: actions/download-artifact@v3
+        with:
+          name: backend-coverage-report
+          path: src/
+      -
+        name: Upload coverage to Codecov
+        uses: codecov/codecov-action@v3
+        with:
+          # not required for public repos, but intermittently fails otherwise
+          token: ${{ secrets.CODECOV_TOKEN }}
+          # future expansion
+          flags: backend
+          directory: src/
+
   build-docker-image:
     name: Build Docker image for ${{ github.ref_name }}
     runs-on: ubuntu-22.04