From c65392cf815694ee899d76bb52185d754298d19c Mon Sep 17 00:00:00 2001 From: Mateo Di Loreto <101841200+mdiloreto@users.noreply.github.com> Date: Tue, 22 Jul 2025 12:53:10 -0300 Subject: [PATCH] Replace non-root Dockerfile base with Alpine multi-stage build; (#12707) * Change Dockerfile.noon_root with alpine base image * Improve non_root docker image * Re add the build_admin_ui.sh script step * Re add the build_admin_ui.sh script step * Remove unnecessary workdir set * Remove unnecessary workdir set * Configure chainguard image * A bit of optimization and improve comments * delete extra build_ui script run * Optimizie Dockerfile copy statements --- docker/Dockerfile.non_root | 112 ++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 64 deletions(-) diff --git a/docker/Dockerfile.non_root b/docker/Dockerfile.non_root index 05d38ef27f7..0967033f07f 100644 --- a/docker/Dockerfile.non_root +++ b/docker/Dockerfile.non_root @@ -1,101 +1,85 @@ -# Base image for building -ARG LITELLM_BUILD_IMAGE=python:3.13.1-slim +# Base images +ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/python:latest-dev +ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/python:latest-dev -# Runtime image -ARG LITELLM_RUNTIME_IMAGE=python:3.13.1-slim -# Builder stage +# ----------------- +# Builder Stage +# ----------------- FROM $LITELLM_BUILD_IMAGE AS builder - -# Set the working directory to /app WORKDIR /app -# Set the shell to bash -SHELL ["/bin/bash", "-o", "pipefail", "-c"] - # Install build dependencies -RUN apt-get clean && apt-get update && \ - apt-get install -y gcc g++ python3-dev && \ - rm -rf /var/lib/apt/lists/* +USER root +RUN apk add --no-cache build-base bash \ + && pip install --no-cache-dir --upgrade pip build -RUN pip install --no-cache-dir --upgrade pip && \ - pip install --no-cache-dir build - -# Copy the current directory contents into the container at /app +# Copy project files COPY . . # Build Admin UI RUN chmod +x docker/build_admin_ui.sh && ./docker/build_admin_ui.sh -# Build the package -RUN rm -rf dist/* && python -m build +# Build package and wheel dependencies +RUN rm -rf dist/* && python -m build && \ + pip install dist/*.whl && \ + pip wheel --no-cache-dir --wheel-dir=/wheels/ -r requirements.txt -# There should be only one wheel file now, assume the build only creates one -RUN ls -1 dist/*.whl | head -1 - -# Install the package -RUN pip install dist/*.whl - -# install dependencies as wheels -RUN pip wheel --no-cache-dir --wheel-dir=/wheels/ -r requirements.txt - -# Runtime stage +# ----------------- +# Runtime Stage +# ----------------- FROM $LITELLM_RUNTIME_IMAGE AS runtime - -# Ensure we are root for package installation -USER root -# Update dependencies and clean up - handles debian security issue -RUN apt-get update && apt-get upgrade -y && rm -rf /var/lib/apt/lists/* - WORKDIR /app -# Copy the current directory contents into the container at /app -COPY . . -RUN ls -la /app -# Copy the built wheel from the builder stage to the runtime stage; assumes only one wheel file is present +# Install runtime dependencies +USER root +RUN apk upgrade --no-cache && \ + apk add --no-cache bash + +# Copy only necessary artifacts from builder stage for runtime +COPY --from=builder /app/docker/entrypoint.sh /app/docker/prod_entrypoint.sh /app/docker/ +COPY --from=builder /app/schema.prisma /app/schema.prisma COPY --from=builder /app/dist/*.whl . COPY --from=builder /wheels/ /wheels/ -# Install the built wheel using pip; again using a wildcard if it's the only file -RUN pip install *.whl /wheels/* --no-index --find-links=/wheels/ && rm -f *.whl && rm -rf /wheels +# Install package from wheel and dependencies +RUN pip install *.whl /wheels/* --no-index --find-links=/wheels/ \ + && rm -f *.whl \ + && rm -rf /wheels -# ensure pyjwt is used, not jwt +# Ensure correct JWT library is used (pyjwt not jwt) RUN pip uninstall jwt -y && \ pip uninstall PyJWT -y && \ pip install PyJWT==2.9.0 --no-cache-dir -# Build Admin UI -RUN chmod +x docker/build_admin_ui.sh && ./docker/build_admin_ui.sh - -### Prisma Handling for Non-Root ################################################# -# Prisma allows you to specify the binary cache directory to use +# --- Prisma Handling for Non-Root User --- +# Set Prisma cache directories ENV PRISMA_BINARY_CACHE_DIR=/nonexistent +ENV NPM_CONFIG_CACHE=/.npm -RUN pip install --no-cache-dir nodejs-bin prisma +# Install prisma and make entrypoints executable +RUN pip install --no-cache-dir prisma && \ + chmod +x docker/entrypoint.sh && \ + chmod +x docker/prod_entrypoint.sh -# Make a /non-existent folder and assign chown to nobody -RUN mkdir -p /nonexistent && \ +# Create directories and set permissions for non-root user +RUN mkdir -p /nonexistent /.npm && \ chown -R nobody:nogroup /app && \ - chown -R nobody:nogroup /nonexistent && \ - chown -R nobody:nogroup /usr/local/lib/python3.13/site-packages/prisma/ + chown -R nobody:nogroup /nonexistent /.npm && \ + PRISMA_PATH=$(python -c "import os, prisma; print(os.path.dirname(prisma.__file__))") && \ + chown -R nobody:nogroup $PRISMA_PATH -RUN chmod +x docker/entrypoint.sh -RUN chmod +x docker/prod_entrypoint.sh - -# Run Prisma generate as user = nobody +# Switch to non-root user USER nobody +# Set HOME for prisma generate to have a writable directory +ENV HOME=/app RUN prisma generate -### End of Prisma Handling for Non-Root ######################################### +# --- End of Prisma Handling --- EXPOSE 4000/tcp -USER root -RUN apt-get update && apt-get install -y supervisor && rm -rf /var/lib/apt/lists/* -USER nobody -COPY docker/supervisord.conf /etc/supervisord.conf - -# # Set your entrypoint and command -ENTRYPOINT ["docker/prod_entrypoint.sh"] +# 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"]