NetMonLLMLive / Dockerfile
Mungert's picture
Update Dockerfile
2dbe9ce verified
Raw History Blame Contribute Delete
13.2 kB
# 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"]