From 52e5b3ae98421763545c4a9430ae67432cdf9518 Mon Sep 17 00:00:00 2001 From: tin-berri Date: Thu, 25 Jun 2026 23:41:08 -0700 Subject: [PATCH] build(docker): build the Admin UI from source in a build-platform-pinned stage (#31130) The monolith images shipped whatever UI bundle was committed to litellm/proxy/_experimental/out, so refreshing the UI for a release meant running build_ui.sh out of band and committing the regenerated bundle. Add a ui-builder stage to all three monolith Dockerfiles (root, database, non_root) that compiles the Next.js static export from this exact source and replaces the committed bundle before the final uv sync. The stage is pinned with FROM --platform=$BUILDPLATFORM so the architecture-independent static export compiles once on the native builder even in a multi-arch (linux/amd64,linux/arm64) build, rather than once per target arch under QEMU emulation. The destination is cleared before the COPY because COPY merges directories and would otherwise leave the committed bundle's content-hashed chunks behind alongside the fresh ones. build_admin_ui.sh still runs afterward so the enterprise custom-color override is preserved. The UI base image is pinned by digest to match LITELLM_BUILD_IMAGE, LITELLM_RUNTIME_IMAGE and UV_IMAGE, and .dockerignore now excludes the local .next/out so a developer's build artifacts never enter the context. --- .dockerignore | 2 ++ Dockerfile | 29 ++++++++++++++++++++++++++++- docker/Dockerfile.database | 29 ++++++++++++++++++++++++++++- docker/Dockerfile.non_root | 27 +++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) diff --git a/.dockerignore b/.dockerignore index 6b80caeaf9f..f3a80fee3e4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -49,6 +49,8 @@ build/ *.egg-info/ .DS_Store **/node_modules +ui/litellm-dashboard/.next +ui/litellm-dashboard/out litellm-rust/target/ litellm/rust_bridge/_native*.so *.log diff --git a/Dockerfile b/Dockerfile index 681681f28cc..b6fef1a21fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,33 @@ +# syntax=docker/dockerfile:1.7 + # Base image for building ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 # Runtime image ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 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 + # Builder stage FROM $LITELLM_BUILD_IMAGE AS builder @@ -48,7 +69,13 @@ RUN uv sync --frozen --no-install-project --no-install-workspace --no-default-gr # Copy full source tree COPY . . -# Build Admin UI before final sync +# 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) diff --git a/docker/Dockerfile.database b/docker/Dockerfile.database index 50ef55e3261..b3af953511d 100644 --- a/docker/Dockerfile.database +++ b/docker/Dockerfile.database @@ -1,12 +1,33 @@ +# syntax=docker/dockerfile:1.7 + # Base image for building ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 # Runtime image ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 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 @@ -46,7 +67,13 @@ RUN uv sync --frozen --no-install-project --no-install-workspace --no-default-gr # Copy full source tree COPY . . -# Build Admin UI before final sync +# 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) diff --git a/docker/Dockerfile.non_root b/docker/Dockerfile.non_root index 6bb925aa723..c24cb9008f0 100644 --- a/docker/Dockerfile.non_root +++ b/docker/Dockerfile.non_root @@ -1,11 +1,32 @@ +# syntax=docker/dockerfile:1.7 + # Base images ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 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: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 ARG PROXY_EXTRAS_SOURCE WORKDIR /app @@ -53,6 +74,12 @@ RUN --mount=type=cache,target=/app/.cache/uv,id=litellm-uv-cache \ # 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