calculus-animator / Dockerfile
Rsan0948
feat(space): enable slide rendering via system fonts + pygame
eaed33c
Raw
History Blame Contribute Delete
4.21 kB
# Calculus Animator โ€” Hugging Face Space (Docker SDK) image.
#
# Single-stage build: FastAPI process serves the static ``ui/`` bundle at
# ``/`` and the ``/api/*`` JSON shim that mirrors ``api.bridge.CalculusAPI``
# for the browser-side ``space_bridge.js``.
#
# Slide-render note: SDL2 runtime libs are installed below so a future
# ``pygame==โ€ฆ`` line drop-in to ``requirements.txt`` enables the slide
# render endpoints without rebuilding the OS layer. v1 ships without
# pygame; ``/api/render_learning_slide`` returns ModuleNotFoundError via
# the global error envelope until that line lands.
FROM python:3.11-slim AS base
# โ”€โ”€ OS deps โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# xvfb + libsdl2-* + fontconfig: pygame runtime support (headless rendering
# under SDL_VIDEODRIVER=dummy). Layered first so the Python install layer
# below stays cache-friendly across requirements.txt edits.
RUN apt-get update && apt-get install -y --no-install-recommends \
xvfb \
libsdl2-2.0-0 \
libsdl2-image-2.0-0 \
libsdl2-mixer-2.0-0 \
libsdl2-ttf-2.0-0 \
fontconfig \
fonts-dejavu \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Headless SDL: pygame opens a dummy framebuffer, never touches a GPU/X server.
ENV SDL_VIDEODRIVER=dummy \
PYGAME_HIDE_SUPPORT_PROMPT=1 \
XDG_RUNTIME_DIR=/tmp \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
WORKDIR /app
# โ”€โ”€ Python deps โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Copy requirements first so the install layer caches across source edits.
# We intentionally skip ``requirements-ai.txt`` (chromadb + sentence-transformers
# are heavyweight and unused by the desktop core that the Space serves);
# adding them is a one-line ``-r requirements-ai.txt`` change in this RUN.
COPY requirements.txt ./
RUN pip install --upgrade pip \
&& pip install -r requirements.txt
# โ”€โ”€ Source โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Copy the runtime trees the FastAPI app actually imports; release-only
# scripts and dev fixtures stay out of the image.
COPY ai_tutor/ ./ai_tutor/
COPY api/ ./api/
COPY core/ ./core/
COPY ui/ ./ui/
COPY slide_renderer/ ./slide_renderer/
COPY scripts/ ./scripts/
COPY data/ ./data/
# ``assets/`` (pygame fonts) is intentionally absent on this branch โ€” HF's
# binary-file rule rejects ``.ttf`` files, and slide rendering is not the
# Space's primary surface. ``/api/render_learning_slide`` will surface a
# missing-font error via the structured error envelope; Solve and graph
# endpoints work without it. Restore via Git LFS if slide rendering is
# wanted on the Space.
# ``config.py`` lives at the project root and is imported by ``run.py`` and
# tests; keep it on the image so any module that walks ``sys.path[0]``
# resolves it the same way as on the host.
COPY config.py ./config.py
# โ”€โ”€ Non-root runtime โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Hugging Face Spaces require the container to run as a non-root user (UID
# 1000 is the convention HF documents). Create the user, hand it ownership of
# /app, and switch.
RUN useradd --create-home --uid 1000 appuser \
&& chown -R appuser:appuser /app
USER appuser
EXPOSE 7860
# ``--host 0.0.0.0`` is required for HF Spaces (host can only route to the
# container via the published port). Loopback-only binding lives on the
# desktop branch's ``run.py`` path; this image's threat model is the
# Space-managed reverse proxy in front of the container.
CMD ["uvicorn", "ai_tutor.main:app", "--host", "0.0.0.0", "--port", "7860"]