diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 85f84e34886fc98403b518e58a30acf94c4cac36..c9d8e23109e4ce2b467b9b0affc75aed05d87c63 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: python:3.11-slim
+image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/python:3.11-slim
 
 variables:
   PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
@@ -16,6 +16,7 @@ variables:
   DB_USER: "random"
   DB_DATABASE: "random"
   DB_HOST: "random"
+  OTLP_GRPC_ENDPOINT: ""
 
 cache:
   paths:
@@ -44,7 +45,7 @@ integration-test-job: # Runs integration tests with the database
     DB_DATABASE: "integration-test-db"
     DB_HOST: "integration-test-db"
   services:
-    - name: mysql:8
+    - name: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/mysql:8
       alias: integration-test-db
       variables:
         MYSQL_RANDOM_ROOT_PASSWORD: "yes"
@@ -72,7 +73,7 @@ e2e-test-job: # Runs e2e tests on the API endpoints
     DB_DATABASE: "e2e-test-db"
     DB_HOST: "e2e-test-db"
   services:
-    - name: mysql:8
+    - name: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/mysql:8
       alias: e2e-test-db
       variables:
         MYSQL_RANDOM_ROOT_PASSWORD: "yes"
@@ -132,7 +133,7 @@ lint-test-job: # Runs linters checks on code
 build-publish-dev-docker-container-job:
   stage: deploy
   image:
-    name: gcr.io/kaniko-project/executor:v1.9.1-debug
+    name: gcr.io/kaniko-project/executor:v1.17.0-debug
     entrypoint: [""]
   dependencies: []
   only:
@@ -155,7 +156,7 @@ build-publish-dev-docker-container-job:
 publish-docker-container-job:
   stage: deploy
   image:
-    name: gcr.io/kaniko-project/executor:v1.9.1-debug
+    name: gcr.io/kaniko-project/executor:v1.17.0-debug
     entrypoint: [""]
   dependencies: []
   only:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2161797e7d3589a81c59be0d81d15cce651eb2f4..16bfbe8c289ab98164d21cbdcb1d0a8c9c8bcde2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -21,7 +21,7 @@ repos:
         files: app
         args: [--check]
 -   repo: https://github.com/charliermarsh/ruff-pre-commit
-    rev: 'v0.1.3'
+    rev: 'v0.1.4'
     hooks:
     -   id: ruff
 -   repo: https://github.com/PyCQA/isort
diff --git a/app/main.py b/app/main.py
index eb2fad4cbdaaa41d2bccf8fe1a263e7b9c290618..d2da35c4ac14dd6144f3e7bad918fc46aa9cf8c8 100644
--- a/app/main.py
+++ b/app/main.py
@@ -1,9 +1,10 @@
-from fastapi import FastAPI, Request, Response
+from hashlib import md5
+
+from fastapi import FastAPI, Request, Response, status
 from fastapi.exception_handlers import http_exception_handler, request_validation_exception_handler
 from fastapi.exceptions import RequestValidationError, StarletteHTTPException
-from fastapi.middleware.cors import CORSMiddleware
 from fastapi.middleware.gzip import GZipMiddleware
-from fastapi.responses import JSONResponse, RedirectResponse
+from fastapi.responses import JSONResponse
 from fastapi.routing import APIRoute
 from opentelemetry import trace
 from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
@@ -38,9 +39,10 @@ app = FastAPI(
     generate_unique_id_function=custom_generate_unique_id,
     # license_info={"name": "MIT", "url": "https://mit-license.org/"},
     root_path=settings.API_PREFIX,
+    openapi_url=None,  # create it manuale to enable caching on client side
 )
 
-if settings.OTLP_GRPC_ENDPOINT is not None and len(settings.OTLP_GRPC_ENDPOINT) > 0:
+if settings.OTLP_GRPC_ENDPOINT is not None and len(settings.OTLP_GRPC_ENDPOINT) > 0:  # pragma: no cover
     resource = Resource(attributes={SERVICE_NAME: "clowm-s3proxy-service"})
     provider = TracerProvider(resource=resource)
     provider.add_span_processor(
@@ -67,14 +69,14 @@ FastAPIInstrumentor.instrument_app(
     app, excluded_urls="health,docs,openapi.json", tracer_provider=trace.get_tracer_provider()
 )
 
-# CORS Settings for the API
-app.add_middleware(
-    CORSMiddleware,
-    allow_origins=settings.BACKEND_CORS_ORIGINS,
-    allow_credentials=False,
-    allow_methods=["*"],
-    allow_headers=["*"],
-)
+#  CORS Settings for the API (disabled)
+# app.add_middleware(
+#     CORSMiddleware,
+#     allow_origins=settings.BACKEND_CORS_ORIGINS,
+#     allow_credentials=False,
+#     allow_methods=["*"],
+#     allow_headers=["*"],
+# )
 
 # Enable gzip compression for large responses
 app.add_middleware(GZipMiddleware, minimum_size=500)
@@ -83,7 +85,21 @@ app.add_middleware(GZipMiddleware, minimum_size=500)
 app.include_router(api_router)
 app.include_router(miscellaneous_router)
 
+# Create Custom route for OpenAPI schema
+# Hash openapi.json content and save it
+m = md5()
+m.update(JSONResponse(app.openapi()).body)  # ensure the serialization is the same as the one in the route
+openapi_hash = m.hexdigest()
+del m
+
+
+# Route for openapi.json file with ETag header for client cache support
+async def openapi(req: Request) -> Response:
+    # If schema on clients side is still valid, return empty Body with 304 response code (client will use cached body)
+    if req.headers.get("If-None-Match") == openapi_hash:
+        return Response(status_code=status.HTTP_304_NOT_MODIFIED)
+    # Return openapi.json with ETag header
+    return JSONResponse(app.openapi(), headers={"ETag": openapi_hash})
+
 
-@app.get("/", response_class=RedirectResponse, tags=["Miscellaneous"], include_in_schema=False)
-def redirect_docs() -> str:
-    return settings.API_PREFIX + "/docs"
+app.add_route("/openapi.json", openapi, include_in_schema=False)
diff --git a/app/tests/api/test_miscellaneous_enpoints.py b/app/tests/api/test_miscellaneous_enpoints.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5b8e7ae03d6861cf65179ccd7051dc747712555
--- /dev/null
+++ b/app/tests/api/test_miscellaneous_enpoints.py
@@ -0,0 +1,39 @@
+import pytest
+from fastapi import status
+from httpx import AsyncClient
+
+
+class TestHealthRoute:
+    @pytest.mark.asyncio
+    async def test_health_route(self, client: AsyncClient) -> None:
+        """
+        Test service health route
+
+        Parameters
+        ----------
+        client : httpx.AsyncClient
+            HTTP Client to perform the request on.
+        """
+        response = await client.get("/health")
+        assert response.status_code == status.HTTP_200_OK
+        body = response.json()
+        assert body["status"] == "OK"
+
+
+class TestOpenAPIRoute:
+    @pytest.mark.asyncio
+    async def test_openapi_route(self, client: AsyncClient) -> None:
+        """
+        Test getting the OpenAPI specification and the caching mechanism.
+
+        Parameters
+        ----------
+        client : httpx.AsyncClient
+            HTTP Client to perform the request on.
+        """
+        response1 = await client.get("/openapi.json")
+        assert response1.status_code == status.HTTP_200_OK
+        assert response1.headers.get("ETag") is not None
+
+        response2 = await client.get("/openapi.json", headers={"If-None-Match": response1.headers.get("ETag")})
+        assert response2.status_code == status.HTTP_304_NOT_MODIFIED
diff --git a/pyproject.toml b/pyproject.toml
index 60fd4460d5544be92c5582c5df69cd31957bad57..41a45c75c40ca325acce1a85c1ded4b4e1574a22 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -25,10 +25,7 @@ omit = [
     "app/check_database_connection.py",
     "app/check_ceph_connection.py",
     "app/check_oidc_connection.py",
-    "app/db/base*",
     "app/core/config.py",
-    "app/main.py",
-    "app/api/miscellaneous_endpoints.py"
 ]
 
 [tool.coverage.report]
diff --git a/requirements.txt b/requirements.txt
index 564a676a5b96a19b341f8b1fd001c8cb2927d694..03e1aa635fa6cf9d3158c0a4cb0b52d7861977d7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,7 +6,7 @@ anyio>=3.7.0,<4.0.0
 fastapi>=0.104.0,<0.105.0
 pydantic>=2.4.0,<2.5.0
 pydantic-settings>=2.0.0,<2.1.0
-uvicorn>=0.23.0,<0.24.0
+uvicorn>=0.24.0,<0.25.0
 # Database packages
 PyMySQL>=1.1.0,<1.2.0
 SQLAlchemy>=2.0.0,<2.1.0