# syntax=docker/dockerfile:1 # ────────────────────────────────────────────────────────────────────────────── # Vantage — single-container demo image. # • Stage 1 (web): build the React/Vite frontend → static dist/ # • Stage 2 (app): FastAPI + agent + baked ML models, serves API *and* the dist # Listens on 7860 (Hugging Face Spaces' expected port; override with $PORT). # Secrets (Qdrant/Neo4j/OpenAI/Langfuse) are injected as env vars at run time — # never baked in. See .env.example for the variable names. # ────────────────────────────────────────────────────────────────────────────── # ── Stage 1: build the frontend ─────────────────────────────────────────────── FROM node:20-slim AS web WORKDIR /web COPY apps/web/package.json apps/web/package-lock.json ./ RUN npm ci COPY apps/web/ ./ RUN npm run build # → /web/dist # ── Stage 2: python API + models + static frontend ─────────────────────────── FROM python:3.11-slim AS app ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ PIP_NO_CACHE_DIR=1 \ HF_HOME=/models \ PORT=7860 WORKDIR /app # curl is used by the container HEALTHCHECK below. RUN apt-get update && apt-get install -y --no-install-recommends curl \ && rm -rf /var/lib/apt/lists/* # CPU-only torch first — avoids pulling the multi-GB CUDA build that # sentence-transformers would otherwise drag in. RUN pip install --index-url https://download.pytorch.org/whl/cpu torch==2.12.0 # Pinned runtime deps (matches the working py311 env), then the local package # with --no-deps so its version ranges can't bump anything we just pinned. COPY requirements.docker.txt ./ RUN pip install -r requirements.docker.txt COPY packages/ ./packages/ RUN pip install --no-deps -e ./packages/vantage_core # Bake the ML models into the image so runtime stays fully offline # (the adapters open them with local_files_only=True). RUN python - <<'PY' from sentence_transformers import SentenceTransformer, CrossEncoder from fastembed import SparseTextEmbedding SentenceTransformer("all-MiniLM-L6-v2") CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2") list(SparseTextEmbedding("Qdrant/bm25").embed(["warmup"])) print("models baked") PY # Application code + built frontend. COPY apps/ ./apps/ COPY --from=web /web/dist ./apps/web/dist EXPOSE 7860 HEALTHCHECK --interval=30s --timeout=5s --start-period=120s --retries=3 \ CMD curl -fsS "http://localhost:${PORT}/api/health" || exit 1 # shell form so ${PORT} expands (HF/Railway/Render set their own PORT). CMD uvicorn apps.api.main:app --host 0.0.0.0 --port ${PORT}