# syntax=docker/dockerfile:1.7 # ---------- Stage 1: official llama.cpp server image ---------- # Use the upstream prebuilt server-only image (~71 MB) instead of compiling # from source. This dropped Space build time from ~30 min (compiling all of # mtmd/clip/vision-models on a 2-vCPU runner) to ~30 sec, and the upstream # image is built/tested by the llama.cpp maintainers for every release. # Pin to a tag (overridable) for reproducibility; defaults to :server. ARG LLAMA_CPP_IMAGE=ghcr.io/ggml-org/llama.cpp:server FROM ${LLAMA_CPP_IMAGE} AS llama # ---------- Stage 2: download model ---------- FROM ubuntu:24.04 AS modelfetch ARG MODEL_REPO=unsloth/gemma-4-E2B-it-GGUF ARG MODEL_FILE=gemma-4-E2B-it-UD-Q4_K_XL.gguf ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ curl \ && rm -rf /var/lib/apt/lists/* WORKDIR /model # Download GGUF from Hugging Face. URL is constructed only from build-args # (not from any user-controlled runtime input), so this does not violate # the rule against using user input in network/file paths at runtime. RUN set -eux; \ url="https://huggingface.co/${MODEL_REPO}/resolve/main/${MODEL_FILE}?download=true"; \ echo "Downloading ${url}"; \ curl -fL --retry 5 --retry-delay 5 --retry-all-errors \ -o "/model/${MODEL_FILE}" \ "${url}"; \ ls -lh "/model/${MODEL_FILE}" # ---------- Stage 3: runtime ---------- # Must match the upstream :server image's glibc/libstdc++ ABI. Upstream is # built on ubuntu:24.04 (glibc 2.39, libstdc++ from gcc-14); debian bookworm # only has glibc 2.36 / older libstdc++ and the binary fails to load with # "GLIBC_2.38 not found" / "GLIBCXX_3.4.32 not found". FROM ubuntu:24.04 AS runtime ENV DEBIAN_FRONTEND=noninteractive # Slim runtime: only what llama-server actually links against (libgomp for # OpenMP) plus curl for HEALTHCHECK and tini for PID 1. The upstream :server # image is built without OpenBLAS, so no libopenblas0 needed; libcurl4 is # pulled in because the upstream image has LLAMA_CURL=ON, but we never use # the --hf-repo path at runtime — it's a small (~6 MB) cost we accept to # avoid maintaining our own build. RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ curl \ libcurl4 \ libgomp1 \ tini \ && apt-get autoremove -y \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* ARG MODEL_FILE=gemma-4-E2B-it-UD-Q4_K_XL.gguf # Hugging Face Spaces requires a non-root user with UID 1000 and a writable # /home/user directory. ubuntu:24.04 ships with a default `ubuntu` user # already at UID 1000, so we rename that user/group/home to `user` instead # of creating a new one (which would fail with "UID 1000 is not unique"). RUN userdel -r ubuntu 2>/dev/null || true \ && groupadd -g 1000 user \ && useradd -m -u 1000 -g 1000 -s /bin/bash user # Upstream :server image is built with GGML_BACKEND_DL=ON, meaning the CPU # backend (libggml-cpu-*.so) is a runtime plugin that llama-server dlopens # from its own directory. Keep the binary and ALL its *.so* siblings together # in /app and add /app to the dynamic linker path so both PUBLIC libs and # DL-plugin backends resolve correctly. Splitting them across /usr/local/bin # vs /usr/local/lib breaks plugin discovery ("no backends are loaded"). COPY --from=llama /app/ /app/ RUN echo "/app" > /etc/ld.so.conf.d/llama.conf && ldconfig \ && ln -sf /app/llama-server /usr/local/bin/llama-server COPY --from=modelfetch /model/${MODEL_FILE} /models/${MODEL_FILE} RUN chown -R user:user /models COPY --chown=user:user entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh USER user WORKDIR /home/user ENV MODEL_PATH=/models/${MODEL_FILE} \ PORT=7860 \ HOST=0.0.0.0 \ CTX_SIZE=4096 \ THREADS=2 \ THREADS_BATCH=4 \ PARALLEL=1 \ N_PREDICT=-1 \ OMP_WAIT_POLICY=active \ LLAMA_API_KEY="" EXPOSE 7860 HEALTHCHECK --interval=30s --timeout=10s --start-period=120s --retries=3 \ CMD curl -fsS "http://127.0.0.1:${PORT}/health" || exit 1 ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"]