| # ββ Base image ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Alpine (musl libc), not Debian slim (glibc) β the abetlen prebuilt CPU | |
| # wheel for llama-cpp-python is itself linked against musl (confirmed via | |
| # readelf: NEEDED libc.musl-x86_64.so.1), even though its filename is | |
| # tagged with the generic "linux_x86_64" platform tag rather than a | |
| # proper musllinux tag. On a glibc image this crashes at import time with | |
| # "OSError: libc.musl-x86_64.so.1: cannot open shared object file". | |
| # All other compiled deps here (pydantic-core, uvloop, httptools) publish | |
| # real musllinux wheels, so nothing needs to compile from source. | |
| FROM python:3.11-alpine | |
| # ββ Environment βββββββββββββββββββββββββββββββββββββββββββββββ | |
| ENV PYTHONUNBUFFERED=1 \ | |
| PYTHONDONTWRITEBYTECODE=1 \ | |
| PIP_NO_CACHE_DIR=1 \ | |
| PIP_DISABLE_PIP_VERSION_CHECK=1 | |
| # ββ System dependencies βββββββββββββββββββββββββββββββββββββββ | |
| # Runtime-only β libstdc++/libgomp satisfy the wheel's NEEDED entries | |
| # (ggml-cpu uses OpenMP + C++). No compiler needed since nothing builds | |
| # from source. | |
| RUN apk add --no-cache curl libstdc++ libgomp | |
| # ββ Work directory ββββββββββββββββββββββββββββββββββββββββββββ | |
| WORKDIR /app | |
| # ββ Python dependencies βββββββββββββββββββββββββββββββββββββββ | |
| # Install llama-cpp-python from precompiled CPU wheels (no compile β fast!) | |
| # Pinned version + --only-binary forces pip to fail fast if the abetlen wheel | |
| # index ever stops publishing a matching wheel, instead of silently falling | |
| # back to a from-source cmake build that blows past HF's build timeout. | |
| # Plain llama-cpp-python (no [server] extra) β we load the model directly | |
| # with the Llama class and implement the OpenAI-style routes ourselves, | |
| # rather than running llama_cpp.server's own bundled FastAPI app (which had | |
| # a bug in its generic error-handling path that returned empty 400s on every | |
| # proxied request). Then install our own minimal FastAPI stack. | |
| RUN pip install --upgrade pip && \ | |
| pip install \ | |
| --only-binary=llama-cpp-python \ | |
| "llama-cpp-python==0.3.19" \ | |
| --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu && \ | |
| pip install \ | |
| fastapi \ | |
| "uvicorn[standard]" \ | |
| huggingface_hub \ | |
| pydantic | |
| # ββ Copy app ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| COPY app.py . | |
| # ββ Port ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| EXPOSE 7860 | |
| # ββ Default environment (override via Space Settings) βββββββββ | |
| # SPACE_ROLE: "reasoning" | "uncensored" | "general" (default: general) β | |
| # picks the curated model automatically. Deliberately NOT setting MODEL_ID | |
| # here: a hardcoded ENV MODEL_ID would always exist in os.environ, so | |
| # app.py's `os.environ.get("MODEL_ID", ROLE_TO_MODEL_ID[SPACE_ROLE])` | |
| # fallback could never trigger β every Space would pin to that value | |
| # regardless of SPACE_ROLE. (This bit us once: this line used to read | |
| # `ENV MODEL_ID="qwen"` and silently overrode SPACE_ROLE on every Space.) | |
| # MODEL_ID / MODEL_REPO / MODEL_FILE: optional raw overrides, set only as a | |
| # Space Variable if you want to bypass role-based selection entirely. | |
| # N_CTX: context window tokens (default: 8192) | |
| # N_THREADS: CPU threads (default: 4) | |
| # API_KEY: optional bearer token to protect the API | |
| # CONFIG_URL: optional URL to a JSON config (for multi-Space clusters) | |
| ENV N_CTX="8192" \ | |
| N_THREADS="4" | |
| # ββ Launch ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| CMD ["python", "app.py"] | |