# syntax=docker/dockerfile:1.7 # --------------------------------------------------------------------------- # Cybersecurity Panel — single-container HuggingFace Spaces image. # # Mirrors the structural choices used by the working HF Spaces deployments # CCAI-Vibe-Demo and CU-Student-AIProject-Helper: # * multi-stage Node frontend build → Python+SPA bundle # * non-root user uid 1000, port 7860 (HF Spaces convention) # * persistence on the HF Storage Bucket mount at /data (SQLite shim) # * REACT_APP_API_URL is set to the empty string at build time so the SPA # issues relative URLs and shares the FastAPI origin # # Build context: repo root (this file). # --------------------------------------------------------------------------- # ---- 1. Frontend build (CRA) ---------------------------------------------- FROM node:20-bookworm AS frontend-build WORKDIR /app/frontend COPY phd-advisor-frontend/package.json phd-advisor-frontend/package-lock.json* ./ RUN --mount=type=cache,target=/root/.npm \ npm ci COPY phd-advisor-frontend/ ./ # Empty REACT_APP_API_URL → CRA inlines '' so every fetch() hits the same # origin as the SPA (the FastAPI server below). ENV REACT_APP_API_URL="" RUN npm run build # ---- 2. Python runtime + SPA bundle --------------------------------------- FROM python:3.12-slim-bookworm # ffmpeg is required by the /api/voice/transcribe endpoint # (browser WebM/Opus → WAV for Whisper). Kept on the runtime image only, # not on the build image, so we don't pay the apt cost during incremental # code rebuilds. RUN apt-get update && \ apt-get install -y --no-install-recommends ffmpeg && \ rm -rf /var/lib/apt/lists/* # HF Spaces runs the container as uid 1000 and expects the app to do the # same; with a non-root user we cannot bind privileged ports, but :7860 is # fine. RUN useradd -m -u 1000 user USER user ENV HOME=/home/user \ PATH=/home/user/.local/bin:$PATH \ PYTHONUNBUFFERED=1 \ CORS_ORIGINS=* \ DATA_DIR=/data \ CONFIG_PATH=/home/user/app/cybersecurity_config.yaml WORKDIR $HOME/app # ---- Python deps (cached unless requirements.txt changes) ----------------- COPY --chown=user multi_llm_chatbot_backend/requirements.txt ./ RUN --mount=type=cache,target=/home/user/.cache/pip,uid=1000,gid=1000 \ pip install --no-cache-dir --user -r requirements.txt # ---- Backend source ------------------------------------------------------- COPY --chown=user multi_llm_chatbot_backend/ ./ # ---- Top-level configuration files (config.yaml + persona definitions) ---- COPY --chown=user cybersecurity_config.yaml ./cybersecurity_config.yaml COPY --chown=user phd_config.yaml ./phd_config.yaml COPY --chown=user undergrad_config.yaml ./undergrad_config.yaml COPY --chown=user personas/ ./personas/ # ---- Frontend bundle ------------------------------------------------------ # main.py mounts $HOME/app/static at "/" so the SPA is served same-origin # with the API. COPY --chown=user --from=frontend-build /app/frontend/build ./static ENV PYTHONPATH=$HOME/app EXPOSE 7860 CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]