initial import: read-only gufo + vLLM dashboard
Build and Push Container / build-and-push (push) Successful in 38s
Build and Push Container / build-and-push (push) Successful in 38s
Single-file FastAPI proxy plus a no-CDN vanilla-JS page for watching a gufo
server and a vLLM server side by side: one Prometheus parser (class Prom) with
per-engine field maps, measured Δcounter/Δt rates for engines that export no
rate gauge, and strict read-only GET-only routing. Engines are opt-in via
*_BASE_URL — unset means no panel, no poll, and a 404 {"configured": false}
without dialling.
Includes the container image and CI so the dashboard can be deployed as an
image rather than a checkout:
- Containerfile: uv-locked two-stage build on python:3.14-slim, non-root
(uid 10001), binds 8090 (8000 is vLLM's port in this fleet), healthcheck on
/api/config so a dead engine never marks the dashboard unhealthy. No frontend
build stage — static/index.html is bundler-free by design and is copied as-is.
- .gitea/workflows/build-and-push.yml: publishes
gitea.reeseapps.com/Experimental/llm-monitor on main + release tags.
114 tests, no network and no real engines required.
LLM-Generated: true
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# llm_monitor — production image (Podman/Docker compatible).
|
||||
#
|
||||
# Stage 1 (python): install the locked dependencies with uv (deps only — the app is
|
||||
# a single module, there is nothing to build or install).
|
||||
# Stage 2 (runtime): slim, non-root, no build tools.
|
||||
#
|
||||
# There is deliberately NO frontend build stage: static/index.html is hand-rolled vanilla
|
||||
# JS + inline SVG with no bundler and no CDN (the engines live on an internet-disconnected
|
||||
# podman network, so a CDN asset would be a dead request and a blank chart). The file is
|
||||
# copied verbatim and served as-is.
|
||||
#
|
||||
# podman build -t localhost/llm-monitor:main -f Containerfile .
|
||||
# podman run -d --name llm-monitor -p 8090:8090 \
|
||||
# -e GUFO_BASE_URL=http://deskwork-ai:8731 \
|
||||
# -e VLLM_BASE_URL=http://driveripper-ai:8000 \
|
||||
# localhost/llm-monitor:main
|
||||
#
|
||||
# Both env vars are optional — unset means this deployment does not run that engine, the
|
||||
# proxy answers 404 {"configured": false} without dialling, and the page drops the panel.
|
||||
# Nothing is baked into the image: no host guesses, no keys.
|
||||
#
|
||||
# Both stages must stay on the SAME Python (3.14): stage 1 builds the venv that stage 2
|
||||
# copies verbatim, so a mismatch would ship a venv the runtime interpreter cannot import.
|
||||
# requires-python is >=3.10; 3.14 is what the box that runs this is on.
|
||||
|
||||
# ---------- Stage 1: python dependencies ----------
|
||||
FROM docker.io/python:3.14-slim AS python
|
||||
WORKDIR /app
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
||||
ENV UV_LINK_MODE=copy \
|
||||
UV_PYTHON_DOWNLOADS=0 \
|
||||
UV_PROJECT_ENVIRONMENT=/app/.venv
|
||||
# Lockfile first so dependency layers cache independently of the app source.
|
||||
COPY pyproject.toml uv.lock ./
|
||||
RUN uv sync --frozen --no-dev --no-cache
|
||||
|
||||
# ---------- Stage 2: runtime ----------
|
||||
FROM docker.io/python:3.14-slim AS runtime
|
||||
ENV PATH="/app/.venv/bin:$PATH" \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
RUN useradd --create-home --uid 10001 monitor
|
||||
WORKDIR /app
|
||||
COPY --from=python /app/.venv /app/.venv
|
||||
COPY app.py ./app.py
|
||||
COPY static ./static
|
||||
RUN chown -R monitor:monitor /app
|
||||
USER monitor
|
||||
# The dashboard listens on 8090 on purpose: 8000 is vLLM's port in this fleet and 8731 is
|
||||
# gufo's, so binding either would collide with an engine (or scrape itself).
|
||||
EXPOSE 8090
|
||||
ENV DASHBOARD_HOST=0.0.0.0 \
|
||||
DASHBOARD_PORT=8090
|
||||
# /api/config answers 200 with no upstream configured and never dials an engine, so a dead
|
||||
# gufo or vLLM can never make the dashboard container unhealthy.
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
||||
CMD ["python", "-c", "import sys, httpx; sys.exit(0 if httpx.get('http://127.0.0.1:8090/api/config', timeout=4).status_code == 200 else 1)"]
|
||||
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8090"]
|
||||
Reference in New Issue
Block a user