diff --git a/Dockerfile b/Dockerfile index 4cef63c8d71..776487df3e7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -142,7 +142,7 @@ USER root RUN echo "https://packages.wolfi.dev/os" >> /etc/apk/repositories # node (without npm) is required by the prisma CLI at runtime -RUN apk add --no-cache bash openssl libssl3 tzdata nodejs python-3.13 libsndfile libevent +RUN apk add --no-cache "py3-pip-wheel>=26.2.2-r1" bash openssl libssl3 tzdata nodejs python-3.13 libsndfile libevent COPY --from=pgbouncer-builder /usr/local/bin/pgbouncer /usr/local/bin/pgbouncer WORKDIR /app diff --git a/backend/Dockerfile b/backend/Dockerfile index aad59ba17c6..d89577c5441 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -110,7 +110,7 @@ ENV LITELLM_RELEASE_TAG=${LITELLM_RELEASE_TAG} USER root RUN for i in 1 2 3; do \ - apk add --no-cache bash openssl libssl3 tzdata python-3.13 libsndfile libatomic && break; \ + apk add --no-cache "py3-pip-wheel>=26.2.2-r1" bash openssl libssl3 tzdata python-3.13 libsndfile libatomic && break; \ [ $i = 3 ] && { echo "apk add failed after 3 retries" >&2; exit 1; }; \ sleep 5; \ done diff --git a/docker/Dockerfile.database b/docker/Dockerfile.database index 20f810686a3..28b63e6c864 100644 --- a/docker/Dockerfile.database +++ b/docker/Dockerfile.database @@ -123,7 +123,7 @@ ENV LITELLM_RELEASE_TAG=${LITELLM_RELEASE_TAG} USER root # node (without npm) is required by the prisma CLI at runtime -RUN apk add --no-cache bash openssl libssl3 tzdata nodejs python-3.13 libsndfile libevent +RUN apk add --no-cache "py3-pip-wheel>=26.2.2-r1" bash openssl libssl3 tzdata nodejs python-3.13 libsndfile libevent COPY --from=pgbouncer-builder /usr/local/bin/pgbouncer /usr/local/bin/pgbouncer WORKDIR /app diff --git a/docker/Dockerfile.non_root b/docker/Dockerfile.non_root index 90bcc523d9e..a4583bbdaf1 100644 --- a/docker/Dockerfile.non_root +++ b/docker/Dockerfile.non_root @@ -135,7 +135,9 @@ RUN for i in 1 2 3; do \ apk upgrade --no-cache && break || sleep 5; \ done && \ for i in 1 2 3; do \ - apk add --no-cache python-3.13 bash openssl libssl3 tzdata libsndfile nodejs libevent && break || sleep 5; \ + apk add --no-cache "py3-pip-wheel>=26.2.2-r1" python-3.13 bash openssl libssl3 tzdata libsndfile nodejs libevent && break; \ + [ $i = 3 ] && { echo "apk add failed after 3 retries" >&2; exit 1; }; \ + sleep 5; \ done COPY --from=pgbouncer-builder /usr/local/bin/pgbouncer /usr/local/bin/pgbouncer diff --git a/gateway/Dockerfile b/gateway/Dockerfile index 658bed831ba..f2778da7661 100644 --- a/gateway/Dockerfile +++ b/gateway/Dockerfile @@ -126,7 +126,7 @@ FROM $LITELLM_RUNTIME_IMAGE AS runtime USER root RUN for i in 1 2 3; do \ - apk add --no-cache bash openssl libssl3 tzdata python-3.13 libsndfile libatomic libevent && break; \ + apk add --no-cache "py3-pip-wheel>=26.2.2-r1" bash openssl libssl3 tzdata python-3.13 libsndfile libatomic libevent && break; \ [ $i = 3 ] && { echo "apk add failed after 3 retries" >&2; exit 1; }; \ sleep 5; \ done diff --git a/migrations/Dockerfile b/migrations/Dockerfile index 3ffa9140543..651129bc85e 100644 --- a/migrations/Dockerfile +++ b/migrations/Dockerfile @@ -119,7 +119,7 @@ FROM $LITELLM_RUNTIME_IMAGE AS runtime USER root RUN for i in 1 2 3; do \ - apk add --no-cache bash openssl libssl3 tzdata python-3.13 nodejs libsndfile libatomic && break; \ + apk add --no-cache "py3-pip-wheel>=26.2.2-r1" bash openssl libssl3 tzdata python-3.13 nodejs libsndfile libatomic && break; \ [ $i = 3 ] && { echo "apk add failed after 3 retries" >&2; exit 1; }; \ sleep 5; \ done diff --git a/tests/integration/database/image/test_offline_image_migration.py b/tests/integration/database/image/test_offline_image_migration.py index be2f0537576..1f999313f29 100644 --- a/tests/integration/database/image/test_offline_image_migration.py +++ b/tests/integration/database/image/test_offline_image_migration.py @@ -17,6 +17,8 @@ image-scan workflow). Requires a working docker CLI. import shutil import subprocess import uuid +from pathlib import Path +from typing import Final import os import pytest @@ -188,3 +190,30 @@ def test_runtime_cache_env_not_read_only(): and line.split("=", 1)[1].startswith("/opt/prisma") ] assert not offenders, f"cache/home env points at the read-only bake: {offenders}" + + +@pytest.mark.parametrize("failed_attempts", [0, 2, 3]) +def test_runtime_apk_install_retries_then_propagates_failure(failed_attempts: int) -> None: + dockerfile: Final = Path(__file__).resolve().parents[4] / "docker" / "Dockerfile.non_root" + runtime: Final = dockerfile.read_text().split("FROM $LITELLM_RUNTIME_IMAGE AS runtime", 1)[1] + install: Final = next( + line.removeprefix("RUN ") + for line in runtime.replace("\\\n", "").splitlines() + if line.startswith("RUN ") and "apk add" in line + ) + commands: Final = f""" +attempt=0 +apk() {{ + [ "$1" = upgrade ] && return 0 + attempt=$((attempt + 1)) + echo install-attempt + [ "$attempt" -gt {failed_attempts} ] +}} +sleep() {{ :; }} +{install} +""" + result: Final = subprocess.run( + ["sh", "-c", commands], capture_output=True, text=True, check=False + ) + assert result.stdout.count("install-attempt") == min(failed_attempts + 1, 3) + assert result.returncode == (1 if failed_attempts == 3 else 0), result.stderr