# syntax=docker/dockerfile:1.7 # Base image for building ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:42df77a9974d6ec8b17a5ee8bc23b532600a44d705acef2409e0933c1251b45f # Runtime image ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:42df77a9974d6ec8b17a5ee8bc23b532600a44d705acef2409e0933c1251b45f 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:20.18-alpine3.20@sha256:3488b10bf958af7125a176419d2d8a9937d895bf124012aae811651988d2ffe6 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 WORKDIR /app USER root COPY --from=uvbin /uv /usr/local/bin/uv COPY --from=uvbin /uvx /usr/local/bin/uvx RUN apk add --no-cache \ bash \ gcc \ python3 \ python3-dev \ openssl \ openssl-dev \ nodejs \ npm \ libsndfile ENV UV_PROJECT_ENVIRONMENT=/app/.venv \ UV_LINK_MODE=copy \ PATH="/app/.venv/bin:${PATH}" # 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 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/ # Build Admin UI before final sync (applies the enterprise color override when present) RUN sed -i 's/\r$//' docker/build_admin_ui.sh && chmod +x docker/build_admin_ui.sh && ./docker/build_admin_ui.sh # Install project and workspace packages (fast - deps already cached) RUN uv sync --frozen --no-default-groups --no-editable \ --extra proxy \ --extra proxy-runtime \ --extra extra_proxy \ --extra semantic-router \ --extra saml \ --python python3 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 USER root # node (without npm) is required by the prisma CLI at runtime RUN apk add --no-cache bash openssl tzdata nodejs python3 libsndfile WORKDIR /app 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 \ PRISMA_OFFLINE_MODE=true # 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 in readOnlyRootFilesystem + emptyDir setups). # The paths are pinned via PRISMA_BINARY_CACHE_DIR / PRISMA_CLI_PATH and # recorded into the generated client at build time, so `prisma migrate # deploy` on a fresh database needs no npm and no network access # (#33650, #24554). COPY --from=builder /opt/prisma /opt/prisma RUN find /app/.venv -type f -path "*/tornado/test/*" -delete && \ find /app/.venv -type d -path "*/tornado/test" -delete && \ 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 EXPOSE 4000/tcp ENTRYPOINT ["docker/prod_entrypoint.sh"] CMD ["--port", "4000"]