# syntax=docker/dockerfile:1.4 # ============================================================ # Stage 1: Build CPU-optimized OpenBLAS + llama.cpp # ============================================================ FROM debian:13 AS native-build ENV DEBIAN_FRONTEND=noninteractive # ============================================================ # Native build dependencies # # Original packages retained: # build-essential # curl # ca-certificates # git # cmake # clang # pkg-config # ccache # wget # # gfortran is required for the source OpenBLAS build. # ============================================================ RUN apt-get update && \ apt-get install -y --no-install-recommends \ build-essential \ curl \ ca-certificates \ git \ cmake \ clang \ pkg-config \ ccache \ wget \ gfortran && \ update-ca-certificates && \ rm -rf /var/lib/apt/lists/* # ============================================================ # Build OpenBLAS from source # # No TARGET is specified. # OpenBLAS detects the build CPU and optimizes for it. # ============================================================ WORKDIR /build RUN git clone --depth 1 \ https://github.com/OpenMathLib/OpenBLAS.git \ /build/OpenBLAS WORKDIR /build/OpenBLAS # Suppress the enormous normal compiler log. # If compilation fails, print the final 100 lines. RUN make -j2 > /tmp/openblas-build.log 2>&1 || \ (echo "OpenBLAS build failed:" && \ tail -100 /tmp/openblas-build.log && \ false) && \ make PREFIX=/opt/OpenBLAS install \ > /tmp/openblas-install.log 2>&1 || \ (echo "OpenBLAS install failed:" && \ tail -100 /tmp/openblas-install.log && \ false) && \ rm -f \ /tmp/openblas-build.log \ /tmp/openblas-install.log # ============================================================ # Build llama.cpp against our source-built OpenBLAS # ============================================================ WORKDIR /build RUN git clone --depth 1 \ https://github.com/ggml-org/llama.cpp.git \ /build/llama.cpp WORKDIR /build/llama.cpp ENV PKG_CONFIG_PATH=/opt/OpenBLAS/lib/pkgconfig ENV LD_LIBRARY_PATH=/opt/OpenBLAS/lib RUN cmake -S . -B build \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_PREFIX_PATH=/opt/OpenBLAS \ -DGGML_NATIVE=ON \ -DGGML_BLAS=ON \ -DGGML_BLAS_VENDOR=OpenBLAS \ -DLLAMA_CURL=OFF && \ cmake --build build \ --config Release \ -j2 # ============================================================ # Extract runtime files # # IMPORTANT: # Use libopenblas* as in your ORIGINAL Dockerfile. # # This copies: # libopenblas.so # libopenblas.so.0 # CPU-specific target library such as: # libopenblas_skylakexp-r0.3.34.dev.so # # This avoids creating broken OpenBLAS symlinks. # ============================================================ RUN mkdir -p \ /out/llama \ /out/openblas && \ cp -a build/bin/. /out/llama/ && \ cp -a /opt/OpenBLAS/lib/libopenblas* /out/openblas/ # ============================================================ # Stage 2: Build .NET application # ============================================================ FROM debian:13 AS dotnet-build ENV DEBIAN_FRONTEND=noninteractive ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 ENV DOTNET_NOLOGO=1 RUN apt-get update && \ apt-get install -y --no-install-recommends \ ca-certificates \ wget \ git \ libicu76 \ libssl3t64 && \ update-ca-certificates && \ rm -rf /var/lib/apt/lists/* # ============================================================ # Create non-root build user # ============================================================ RUN useradd -m user && \ mkdir -p \ /home/user/code \ /home/user/out/app && \ chown -R user:user /home/user USER user WORKDIR /home/user # ============================================================ # Install .NET 10 SDK # ============================================================ RUN wget -q \ https://dot.net/v1/dotnet-install.sh \ -O dotnet-install.sh && \ chmod +x dotnet-install.sh && \ ./dotnet-install.sh \ --channel 10.0 && \ rm dotnet-install.sh ENV DOTNET_ROOT=/home/user/.dotnet ENV PATH="${PATH}:${DOTNET_ROOT}:${DOTNET_ROOT}/tools" # ============================================================ # Clone application repositories # ============================================================ WORKDIR /home/user/code RUN --mount=type=secret,id=GITHUB_TOKEN,mode=0444,required=true \ git clone --depth 1 \ https://x-access-token:$(cat /run/secrets/GITHUB_TOKEN)@github.com/Mungert69/NetworkMonitorLib.git \ /home/user/code/NetworkMonitorLib && \ git clone --depth 1 \ https://x-access-token:$(cat /run/secrets/GITHUB_TOKEN)@github.com/Mungert69/NetworkMonitorLLM.git \ /home/user/code/NetworkMonitorLLM # ============================================================ # Restore and publish .NET application # ============================================================ WORKDIR /home/user/code/NetworkMonitorLLM RUN dotnet restore NetworkMonitorLLM.csproj RUN dotnet publish NetworkMonitorLLM.csproj \ -c Release \ --no-restore \ --no-self-contained \ -o /home/user/out/app # ============================================================ # Stage 3: Runtime image # ============================================================ FROM debian:13 AS runtime ENV DEBIAN_FRONTEND=noninteractive ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 ENV DOTNET_NOLOGO=1 # ============================================================ # Runtime dependencies # # Original runtime utilities retained: # curl # ca-certificates # wget # vim # libicu76 # expect # # Explicit compiler/runtime libraries added because the build # toolchain no longer exists in this multi-stage image: # # libstdc++6 # libgcc-s1 # libgfortran5 # libgomp1 # libquadmath0 # libatomic1 # ============================================================ RUN apt-get update && \ apt-get install -y --no-install-recommends \ ca-certificates \ wget \ curl \ vim \ libicu76 \ libssl3t64 \ libgssapi-krb5-2 \ libstdc++6 \ libgcc-s1 \ libgfortran5 \ libgomp1 \ libquadmath0 \ libatomic1 \ zlib1g \ expect \ tzdata && \ update-ca-certificates && \ rm -rf /var/lib/apt/lists/* # ============================================================ # Install CPU-optimized OpenBLAS # ============================================================ COPY --from=native-build \ /out/openblas/ \ /usr/local/lib/ RUN ldconfig # ============================================================ # Verify OpenBLAS itself was copied correctly # # `test -e` follows the libopenblas.so symlink, so this fails # if the CPU-specific target library was not copied. # ============================================================ RUN test -e /usr/local/lib/libopenblas.so && \ echo "OpenBLAS library verified:" && \ ls -lh /usr/local/lib/libopenblas* # ============================================================ # Create runtime user/directories # ============================================================ RUN useradd -m user && \ mkdir -p \ /home/user/code/app/wwwroot \ /home/user/code/models/llama.cpp \ /home/user/code/models && \ chown -R user:user /home/user USER user WORKDIR /home/user # ============================================================ # Install ONLY ASP.NET Core runtime # ============================================================ RUN wget -q \ https://dot.net/v1/dotnet-install.sh \ -O dotnet-install.sh && \ chmod +x dotnet-install.sh && \ ./dotnet-install.sh \ --channel 10.0 \ --runtime aspnetcore && \ rm dotnet-install.sh ENV DOTNET_ROOT=/home/user/.dotnet ENV PATH="${PATH}:${DOTNET_ROOT}:${DOTNET_ROOT}/tools" # ============================================================ # Runtime dynamic library locations # # llama.cpp puts its shared libraries beside its executables. # Source-built OpenBLAS is in /usr/local/lib. # ============================================================ ENV LD_LIBRARY_PATH="/home/user/code/models/llama.cpp:/usr/local/lib" # ============================================================ # Copy compiled llama.cpp binaries/libraries # ============================================================ COPY --from=native-build --chown=user:user \ /out/llama/ \ /home/user/code/models/llama.cpp/ # ============================================================ # Verify EVERY llama/OpenBLAS dynamic dependency # # This catches missing shared libraries DURING THE DOCKER BUILD # rather than discovering them after the Space starts. # ============================================================ RUN set -eu; \ echo "Checking llama.cpp runtime dependencies..."; \ missing=0; \ for f in /home/user/code/models/llama.cpp/*; do \ [ -f "$f" ] || continue; \ deps="$(ldd "$f" 2>/dev/null || true)"; \ if printf '%s\n' "$deps" | grep -q "not found"; then \ echo "========================================"; \ echo "MISSING DEPENDENCY IN: $f"; \ printf '%s\n' "$deps"; \ echo "========================================"; \ missing=1; \ fi; \ done; \ for f in /usr/local/lib/libopenblas*; do \ [ -f "$f" ] || continue; \ deps="$(ldd "$f" 2>/dev/null || true)"; \ if printf '%s\n' "$deps" | grep -q "not found"; then \ echo "========================================"; \ echo "MISSING DEPENDENCY IN: $f"; \ printf '%s\n' "$deps"; \ echo "========================================"; \ missing=1; \ fi; \ done; \ if [ "$missing" -ne 0 ]; then \ echo "One or more runtime libraries are missing."; \ exit 1; \ fi; \ echo "All llama.cpp/OpenBLAS dynamic dependencies resolved." # ============================================================ # Copy published .NET application # ============================================================ COPY --from=dotnet-build --chown=user:user \ /home/user/out/app/ \ /home/user/code/app/ # ============================================================ # Application configuration # ============================================================ COPY --chown=user:user \ appsettings.json \ /home/user/code/app/appsettings.json COPY --chown=user:user \ index.html \ /home/user/code/app/wwwroot/index.html # ============================================================ # Model configuration # # Model is deliberately NOT put into the Docker image. # It is downloaded after the Space starts so the 6.5GB GGUF # does not have to be exported/compressed/pushed as an image # layer. # ============================================================ ENV MODEL_DIR=/home/user/code/models ENV MODEL_FILE=Mellum2-12B-A2.5B-Instruct-mxfp4_moe.gguf ENV MODEL_URL=https://huggingface.co/Mungert/Mellum2-12B-A2.5B-Instruct-GGUF/resolve/main/Mellum2-12B-A2.5B-Instruct-mxfp4_moe.gguf # ============================================================ # Create startup script # ============================================================ RUN cat > /home/user/start.sh <<'EOF' #!/bin/sh set -e MODEL_PATH="${MODEL_DIR}/${MODEL_FILE}" TEMP_PATH="${MODEL_PATH}.download" echo "==========================================" echo "NetworkMonitorLLM startup" echo "==========================================" echo "Model:" echo "${MODEL_PATH}" echo echo "OpenBLAS:" ls -lh /usr/local/lib/libopenblas* || true echo echo "llama.cpp:" ls -lh /home/user/code/models/llama.cpp/ | head -20 || true # ============================================================ # Download model at runtime # # Quiet mode prevents the 6.5GB download from exhausting the # Hugging Face web log display. # ============================================================ if [ ! -s "${MODEL_PATH}" ]; then echo echo "Model not present." echo "Downloading model..." rm -f "${TEMP_PATH}" wget -q \ --tries=5 \ --timeout=60 \ -O "${TEMP_PATH}" \ "${MODEL_URL}" echo "Download completed." mv "${TEMP_PATH}" "${MODEL_PATH}" else echo echo "Model already exists." fi echo echo "Model size:" ls -lh "${MODEL_PATH}" # ============================================================ # Start .NET application # ============================================================ echo echo "Starting NetworkMonitorLLM..." cd /home/user/code/app exec dotnet NetworkMonitorLLM.dll \ --urls http://0.0.0.0:7860 EOF RUN chmod +x /home/user/start.sh # ============================================================ # Hugging Face Space # ============================================================ WORKDIR /home/user/code/app EXPOSE 7860 CMD ["/home/user/start.sh"]