# syntax=docker/dockerfile:1.7 # Base images ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:a31344ab2cb8618db84f535eec56f76f6178b142cb92cb2e48676cc2dcebea72 ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:a31344ab2cb8618db84f535eec56f76f6178b142cb92cb2e48676cc2dcebea72 ARG PROXY_EXTRAS_SOURCE=published ARG UV_IMAGE=ghcr.io/astral-sh/uv:0.11.7@sha256:240fb85ab0f263ef12f492d8476aa3a2e4e1e333f7d67fbdd923d00a506a516a # Pinned by digest like the other base images; bump explicitly on Node upgrades. ARG UI_BUILD_IMAGE=node:24.19-alpine3.24@sha256:d32cdf619f63fe0471182d08996dd516c6275bb5fd31ae06e55a570bd9e1ad43 FROM $UV_IMAGE AS uvbin # Admin UI builder. Pinned to the build platform so the architecture-independent # Next.js static export compiles once natively even in a multi-arch build, # instead of once per target arch under QEMU. FROM --platform=$BUILDPLATFORM $UI_BUILD_IMAGE AS ui-builder ENV NEXT_TELEMETRY_DISABLED=1 \ npm_config_fund=false \ npm_config_audit=false WORKDIR /ui COPY ui/litellm-dashboard/package.json ui/litellm-dashboard/package-lock.json ./ RUN --mount=type=cache,target=/root/.npm npm ci --prefer-offline COPY ui/litellm-dashboard/ ./ RUN npm run build FROM $LITELLM_BUILD_IMAGE AS builder ARG PROXY_EXTRAS_SOURCE WORKDIR /app USER root COPY --from=uvbin /uv /usr/local/bin/uv COPY --from=uvbin /uvx /usr/local/bin/uvx RUN for i in 1 2 3; do \ apk add --no-cache \ python3 \ python3-dev \ gcc \ rust \ bash \ coreutils \ curl \ openssl \ libsndfile \ nodejs \ npm && break || sleep 5; \ done ENV UV_PROJECT_ENVIRONMENT=/app/.venv \ UV_LINK_MODE=copy \ PATH="/app/.venv/bin:${PATH}" \ LITELLM_NON_ROOT=true \ XDG_CACHE_HOME=/app/.cache # Copy dependency metadata first for layer caching COPY pyproject.toml uv.lock ./ COPY enterprise/pyproject.toml enterprise/ COPY litellm-proxy-extras/pyproject.toml litellm-proxy-extras/ # Install third-party dependencies (cached unless pyproject.toml/uv.lock change) RUN --mount=type=cache,target=/app/.cache/uv,id=litellm-uv-cache \ uv sync --frozen --no-install-project --no-install-workspace --no-default-groups --no-editable \ --extra proxy \ --extra proxy-runtime \ --extra extra_proxy \ --extra semantic-router \ --extra saml \ --python python3 # Copy full source tree COPY . . # Replace the committed UI bundle with the one built from this exact source. # Clearing first drops the committed bundle's content-hashed chunks that COPY # would otherwise leave behind alongside the fresh ones. RUN rm -rf litellm/proxy/_experimental/out COPY --from=ui-builder /ui/out/. litellm/proxy/_experimental/out/ # Set non-root flag for build time consistency ENV LITELLM_NON_ROOT=true RUN mkdir -p /var/lib/litellm/ui /var/lib/litellm/assets && \ cp -r /app/litellm/proxy/_experimental/out/. /var/lib/litellm/ui/ && \ cp /app/litellm/proxy/logo.jpg /var/lib/litellm/assets/logo.jpg && \ touch /var/lib/litellm/ui/.litellm_ui_ready RUN --mount=type=cache,target=/app/.cache/uv,id=litellm-uv-cache \ if [ "$PROXY_EXTRAS_SOURCE" = "published" ]; then \ uv sync --frozen --no-default-groups --no-editable \ --extra proxy \ --extra proxy-runtime \ --extra extra_proxy \ --extra semantic-router \ --extra saml \ --python python3 \ --no-sources-package litellm-proxy-extras; \ else \ uv sync --frozen --no-default-groups --no-editable \ --extra proxy \ --extra proxy-runtime \ --extra extra_proxy \ --extra semantic-router \ --extra saml \ --python python3; \ fi RUN HOME=/opt/prisma XDG_CACHE_HOME=/opt/prisma/.cache PRISMA_BINARY_CACHE_DIR=/opt/prisma/binaries \ npm_config_cache=/root/.npm \ prisma generate --schema=./schema.prisma RUN sed -i 's/\r$//' docker/entrypoint.sh && chmod +x docker/entrypoint.sh && \ sed -i 's/\r$//' docker/prod_entrypoint.sh && chmod +x docker/prod_entrypoint.sh FROM $LITELLM_RUNTIME_IMAGE AS runtime ARG PROXY_EXTRAS_SOURCE WORKDIR /app USER root 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 python3 bash openssl tzdata libsndfile nodejs && break || sleep 5; \ done # Copy only what runtime needs. The application is installed inside the venv; # the rest of the builder's /app is source and build metadata that must not # ship (manifest-scanning tools attribute everything in it to this image). # entrypoint.sh invokes litellm/proxy/prisma_migration.py by source path. COPY --from=builder /app/.venv /app/.venv COPY --from=builder /app/docker /app/docker COPY --from=builder /app/schema.prisma /app/schema.prisma COPY --from=builder /app/litellm/proxy/prisma_migration.py /app/litellm/proxy/prisma_migration.py # enterprise/ is imported by source path at runtime (proxy_cli puts the # working directory on sys.path; litellm/proxy/hooks resolves # enterprise.enterprise_hooks from it) COPY --from=builder /app/enterprise /app/enterprise COPY --from=builder /app/litellm-proxy-extras /app/litellm-proxy-extras # Prisma CLI + engines are baked under /opt/prisma, a fixed path every runtime # uid can read and that no cache volume mount shadows (unlike /app/.cache or # $HOME/.cache under readOnlyRootFilesystem + emptyDir or arbitrary-uid setups). # PRISMA_CLI_QUERY_ENGINE_TYPE=binary makes the CLI use the baked binary query # engine directly, so `prisma migrate deploy` on a fresh database needs no npm # and no network access; without it the CLI looks for the library engine, which # prisma stopped baking, and falls back to a download that fails offline or as a # non-writable uid (#33650, #24554). COPY --from=builder /opt/prisma /opt/prisma COPY --from=builder /var/lib/litellm/ui /var/lib/litellm/ui COPY --from=builder /var/lib/litellm/assets /var/lib/litellm/assets # XDG_CACHE_HOME is intentionally left unset so it falls back to $HOME/.cache # (/app/.cache, writable by the runtime uid). The prisma bake at the read-only # /opt/prisma is anchored by PRISMA_BINARY_CACHE_DIR / PRISMA_CLI_PATH, so # nothing needs XDG to point there; pointing it at the read-only bake would # deny any XDG-aware library that writes a cache at runtime. ENV PATH="/app/.venv/bin:${PATH}" \ PRISMA_BINARY_CACHE_DIR=/opt/prisma/binaries \ PRISMA_CLI_PATH=/opt/prisma/binaries/node_modules/.bin/prisma \ PRISMA_CLI_QUERY_ENGINE_TYPE=binary \ HOME=/app \ LITELLM_NON_ROOT=true \ PRISMA_SKIP_POSTINSTALL_GENERATE=1 \ PRISMA_HIDE_UPDATE_MESSAGE=1 \ PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING=1 \ PRISMA_OFFLINE_MODE=true RUN mkdir -p /nonexistent /app/.cache /var/lib/litellm/assets /var/lib/litellm/ui && \ chown -R nobody:nogroup /app /var/lib/litellm/ui /var/lib/litellm/assets /nonexistent && \ PRISMA_PATH=$(python -c "import os, prisma; print(os.path.dirname(prisma.__file__))") && \ chown -R nobody:nogroup "$PRISMA_PATH" && \ LITELLM_PKG_MIGRATIONS_PATH="$(python -c 'import os, litellm_proxy_extras; print(os.path.dirname(litellm_proxy_extras.__file__))' 2>/dev/null || echo '')/migrations" && \ [ -n "$LITELLM_PKG_MIGRATIONS_PATH" ] && chown -R nobody:nogroup "$LITELLM_PKG_MIGRATIONS_PATH" || true && \ LITELLM_PROXY_EXTRAS_PATH=$(python -c "import os, litellm_proxy_extras; print(os.path.dirname(litellm_proxy_extras.__file__))" 2>/dev/null || echo "") && \ chgrp -R 0 "$PRISMA_PATH" /var/lib/litellm/ui /var/lib/litellm/assets && \ [ -n "$LITELLM_PROXY_EXTRAS_PATH" ] && chgrp -R 0 "$LITELLM_PROXY_EXTRAS_PATH" || true && \ chmod -R g=u "$PRISMA_PATH" /var/lib/litellm/ui /var/lib/litellm/assets && \ [ -n "$LITELLM_PROXY_EXTRAS_PATH" ] && chmod -R g=u "$LITELLM_PROXY_EXTRAS_PATH" || true && \ chmod -R g+w "$PRISMA_PATH" /var/lib/litellm/ui /var/lib/litellm/assets && \ [ -n "$LITELLM_PROXY_EXTRAS_PATH" ] && chmod -R g+w "$LITELLM_PROXY_EXTRAS_PATH" || true && \ chmod -R g+rX "$PRISMA_PATH" /var/lib/litellm/ui /var/lib/litellm/assets && \ chmod -R a+rX /opt/prisma && \ test -x /opt/prisma/binaries/node_modules/.bin/prisma && \ test -f /opt/prisma/binaries/node_modules/prisma/build/index.js && \ ls /opt/prisma/binaries/node_modules/@prisma/engines/query-engine-* >/dev/null 2>&1 && \ python -c "from prisma.client import BINARY_PATHS; paths = list(BINARY_PATHS.query_engine.values()); assert paths and all(p.startswith('/opt/prisma/') for p in paths), paths" USER 65534 EXPOSE 4000/tcp ENTRYPOINT ["/app/docker/prod_entrypoint.sh"] CMD ["--port", "4000"]