diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 65cbcd7408fe6c21a6505a5dff646426d0d8df5f..f88d711a1da454b78b4464d12aa62216e78ae833 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -7,17 +7,26 @@ stages:
 run-tests:
   stage: testing
   script:
-    - pipx install --force pip pipenv
-    - pipenv --rm || true
-    - pipenv run pip install -e '.[tests,opensearch2]'
-    - pipenv run ./run-tests.sh
+    - git clean -dfx
+    - uv sync --all-extras --no-progress
+    - source .venv/bin/activate
+    - ./run-tests.sh
+    - deactivate
   coverage: /TOTAL.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/
+  artifacts:
+    paths:
+      - coverage.xml
+    when: on_success
+    access: developer
+    expire_in: "10 mins"
   rules:
     - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
     - if: $CI_COMMIT_TAG =~ /^v\d+/
 
 sonarqube-check:
   stage: testing
+  needs:
+    - run-tests
   variables:
     SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"  # Defines the location of the analysis task cache
     GIT_DEPTH: "0"  # Tells git to fetch all the branches of the project, required by the analysis task
@@ -30,15 +39,16 @@ sonarqube-check:
   allow_failure: true
   rules:
     - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
-    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+    - if: $CI_COMMIT_TAG =~ /^v\d+/
 
 pypi-release:
   stage: release
+  needs:
+    - run-tests
   rules:
     - if: '$CI_COMMIT_TAG =~ /^v\d+/'
   script:
     - rm -f dist/*
-    - pipx run check-manifest
-    - pipx run build
-    - pipx run twine check dist/*
-    - TWINE_USERNAME=${PYPI_USER} TWINE_PASSWORD=${PYPI_PASSWORD} pipx run twine upload --skip-existing --non-interactive dist/*
+    - uv build
+    - uvx twine check dist/*
+    - TWINE_USERNAME=${PYPI_USER} TWINE_PASSWORD=${PYPI_PASSWORD} uvx twine upload --skip-existing --non-interactive dist/*
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 595cb081451e865c4fa631527410e58e5efba831..0000000000000000000000000000000000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2020 FAIR Data Austria.
-#
-# Invenio-Config-TUW is free software; you can redistribute it and/or modify
-# it under the terms of the MIT License; see LICENSE file for more details.
-
-notifications:
-  email: false
-
-sudo: false
-
-language: python
-
-matrix:
-  fast_finish: true
-  allow_failures:
-    # To allow failures, you need to specify the full environment
-    - env: REQUIREMENTS=devel
-
-cache:
-  - pip
-
-env:
-  - REQUIREMENTS=lowest
-  - REQUIREMENTS=release DEPLOY=true
-  - REQUIREMENTS=devel
-
-python:
-  - "3.6"
-  - "3.7"
-  - "3.8"
-
-before_install:
-  - "nvm install 6; nvm use 6"
-  - "travis_retry pip install --upgrade pip setuptools py"
-  - "travis_retry pip install twine wheel coveralls requirements-builder"
-  - "requirements-builder -e all --level=min setup.py > .travis-lowest-requirements.txt"
-  - "requirements-builder -e all --level=pypi setup.py > .travis-release-requirements.txt"
-  - "requirements-builder -e all --level=dev --req requirements-devel.txt setup.py > .travis-devel-requirements.txt"
-
-install:
-  - "travis_retry pip install -r .travis-${REQUIREMENTS}-requirements.txt"
-  - "travis_retry pip install -e .[all]"
-
-script:
-  - "./run-tests.sh"
-
-after_success:
-  - coveralls
-
-deploy:
-  provider: pypi
-  user: inveniosoftware
-  password:
-    secure: TODO:PYPISECUREHASH
-  distributions: "compile_catalog sdist bdist_wheel"
-  on:
-    tags: true
-    python: "3.8"
-    repo: fair-data-austria/invenio-config-tuw
-    condition: $DEPLOY = true