From c91781b1e5dcecab219b5cd47f0e485c3d1a3d5c Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 5 Nov 2025 10:57:59 -0800 Subject: [PATCH] Fix model hub table 404 on non-root docker --- docker/Dockerfile.non_root | 53 ++++++++++++------- litellm/proxy/proxy_server.py | 39 ++++++++++---- .../src/components/chat_ui/CodeSnippets.tsx | 2 +- 3 files changed, 64 insertions(+), 30 deletions(-) diff --git a/docker/Dockerfile.non_root b/docker/Dockerfile.non_root index 4178724e6e4..6be115ba71b 100644 --- a/docker/Dockerfile.non_root +++ b/docker/Dockerfile.non_root @@ -8,16 +8,36 @@ ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/python:latest-dev FROM $LITELLM_BUILD_IMAGE AS builder WORKDIR /app -# Install build dependencies +# Install build dependencies including Node.js for UI build USER root -RUN apk add --no-cache build-base bash \ +RUN apk add --no-cache build-base bash nodejs npm \ && pip install --no-cache-dir --upgrade pip build # Copy project files COPY . . +# Set LITELLM_NON_ROOT flag for build time +ENV LITELLM_NON_ROOT=true + # Build Admin UI -RUN chmod +x docker/build_admin_ui.sh && ./docker/build_admin_ui.sh +RUN mkdir -p /tmp/litellm_ui && \ + cd ui/litellm-dashboard && \ + if [ -f "../../enterprise/enterprise_ui/enterprise_colors.json" ]; then \ + cp ../../enterprise/enterprise_ui/enterprise_colors.json ./ui_colors.json; \ + fi && \ + npm install && \ + npm run build && \ + cp -r ./out/* /tmp/litellm_ui/ && \ + cd /tmp/litellm_ui && \ + for html_file in *.html; do \ + if [ "$html_file" != "index.html" ] && [ -f "$html_file" ]; then \ + folder_name="${html_file%.html}" && \ + mkdir -p "$folder_name" && \ + mv "$html_file" "$folder_name/index.html"; \ + fi; \ + done && \ + cd /app/ui/litellm-dashboard && \ + rm -rf ./out # Build package and wheel dependencies RUN rm -rf dist/* && python -m build && \ @@ -42,6 +62,7 @@ COPY --from=builder /app/docker/supervisord.conf /etc/supervisord.conf COPY --from=builder /app/schema.prisma /app/schema.prisma COPY --from=builder /app/dist/*.whl . COPY --from=builder /wheels/ /wheels/ +COPY --from=builder /tmp/litellm_ui /tmp/litellm_ui # Install package from wheel and dependencies RUN pip install *.whl /wheels/* --no-index --find-links=/wheels/ \ @@ -56,7 +77,6 @@ RUN pip uninstall jwt -y && \ pip uninstall PyJWT -y && \ pip install PyJWT==2.9.0 --no-cache-dir -# --- Prisma Handling for Non-Root User --- # Set Prisma cache directories ENV PRISMA_BINARY_CACHE_DIR=/nonexistent ENV NPM_CONFIG_CACHE=/.npm @@ -68,25 +88,20 @@ RUN pip install --no-cache-dir prisma && \ # Create directories and set permissions for non-root user RUN mkdir -p /nonexistent /.npm && \ - chown -R nobody:nogroup /app && \ - chown -R nobody:nogroup /nonexistent /.npm && \ + chown -R nobody:nogroup /app /tmp/litellm_ui /nonexistent /.npm && \ 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 -# --- OpenShift Compatibility: Apply Red Hat recommended pattern --- -# Get paths for directories that need write access at runtime +# OpenShift compatibility RUN PRISMA_PATH=$(python -c "import os, prisma; print(os.path.dirname(prisma.__file__))") && \ LITELLM_PROXY_EXTRAS_PATH=$(python -c "import os, litellm_proxy_extras; print(os.path.dirname(litellm_proxy_extras.__file__))" 2>/dev/null || echo "") && \ - # Set group ownership to 0 (root group) for OpenShift compatibility && \ - chgrp -R 0 $PRISMA_PATH && \ + chgrp -R 0 $PRISMA_PATH /tmp/litellm_ui && \ [ -n "$LITELLM_PROXY_EXTRAS_PATH" ] && chgrp -R 0 $LITELLM_PROXY_EXTRAS_PATH || true && \ - # Mirror owner permissions to group (g=u) as recommended by Red Hat && \ - chmod -R g=u $PRISMA_PATH && \ + chmod -R g=u $PRISMA_PATH /tmp/litellm_ui && \ [ -n "$LITELLM_PROXY_EXTRAS_PATH" ] && chmod -R g=u $LITELLM_PROXY_EXTRAS_PATH || true && \ - # Ensure directories are writable by group && \ - chmod -R g+w $PRISMA_PATH && \ + chmod -R g+w $PRISMA_PATH /tmp/litellm_ui && \ [ -n "$LITELLM_PROXY_EXTRAS_PATH" ] && chmod -R g+w $LITELLM_PROXY_EXTRAS_PATH || true # Switch to non-root user @@ -94,14 +109,14 @@ USER nobody # Set HOME for prisma generate to have a writable directory ENV HOME=/app + +# Set LITELLM_NON_ROOT flag for runtime +ENV LITELLM_NON_ROOT=true + RUN prisma generate -# --- End of Prisma Handling --- EXPOSE 4000/tcp -# Set entrypoint and command ENTRYPOINT ["/app/docker/prod_entrypoint.sh"] -# Append "--detailed_debug" to the end of CMD to view detailed debug logs -# CMD ["--port", "4000", "--detailed_debug"] -CMD ["--port", "4000"] +CMD ["--port", "4000"] \ No newline at end of file diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 0019365e7e1..bc79198d9fd 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -903,6 +903,20 @@ try: ui_path = os.path.join(current_dir, "_experimental", "out") litellm_asset_prefix = "/litellm-asset-prefix" + # For non-root Docker, use the pre-built UI from /tmp/litellm_ui + # Support both "true" and "True" for case-insensitive comparison + if os.getenv("LITELLM_NON_ROOT", "").lower() == "true": + non_root_ui_path = "/tmp/litellm_ui" + + # Check if the UI was built and exists at the expected location + if os.path.exists(non_root_ui_path) and os.listdir(non_root_ui_path): + verbose_proxy_logger.info(f"Using pre-built UI for non-root Docker: {non_root_ui_path}") + verbose_proxy_logger.info(f"UI files found: {len(os.listdir(non_root_ui_path))} items") + ui_path = non_root_ui_path + else: + verbose_proxy_logger.error(f"UI not found at {non_root_ui_path}. UI will not be available.") + verbose_proxy_logger.error(f"Path exists: {os.path.exists(non_root_ui_path)}, Has content: {os.path.exists(non_root_ui_path) and bool(os.listdir(non_root_ui_path))}") + # Only modify files if a custom server root path is set if server_root_path and server_root_path != "/": # Iterate through files in the UI directory @@ -962,17 +976,22 @@ try: app.mount("/ui", StaticFiles(directory=ui_path, html=True), name="ui") # Handle HTML file restructuring - for filename in os.listdir(ui_path): - if filename.endswith(".html") and filename != "index.html": - # Create a folder with the same name as the HTML file - folder_name = os.path.splitext(filename)[0] - folder_path = os.path.join(ui_path, folder_name) - os.makedirs(folder_path, exist_ok=True) + # Skip this for non-root Docker since it's done at build time + # Support both "true" and "True" for case-insensitive comparison + if os.getenv("LITELLM_NON_ROOT", "").lower() != "true": + for filename in os.listdir(ui_path): + if filename.endswith(".html") and filename != "index.html": + # Create a folder with the same name as the HTML file + folder_name = os.path.splitext(filename)[0] + folder_path = os.path.join(ui_path, folder_name) + os.makedirs(folder_path, exist_ok=True) - # Move the HTML file into the folder and rename it to 'index.html' - src = os.path.join(ui_path, filename) - dst = os.path.join(folder_path, "index.html") - os.rename(src, dst) + # Move the HTML file into the folder and rename it to 'index.html' + src = os.path.join(ui_path, filename) + dst = os.path.join(folder_path, "index.html") + os.rename(src, dst) + else: + verbose_proxy_logger.info("Skipping runtime HTML restructuring for non-root Docker (already done at build time)") except Exception: pass diff --git a/ui/litellm-dashboard/src/components/chat_ui/CodeSnippets.tsx b/ui/litellm-dashboard/src/components/chat_ui/CodeSnippets.tsx index 91e372841f4..f9d5c84fade 100644 --- a/ui/litellm-dashboard/src/components/chat_ui/CodeSnippets.tsx +++ b/ui/litellm-dashboard/src/components/chat_ui/CodeSnippets.tsx @@ -17,7 +17,7 @@ interface GenerateCodeParams { selectedVectorStores: string[]; selectedGuardrails: string[]; selectedMCPTools: string[]; - selectedVoice: string; + selectedVoice?: string; endpointType: string; selectedModel: string | undefined; selectedSdk: "openai" | "azure";