Spaces:
Running
Running
| # syntax=docker/dockerfile:1 | |
| # ---------- Stage 1: build del frontend (Vite + TS) ---------- | |
| FROM node:22-slim AS frontend | |
| WORKDIR /build | |
| COPY frontend/package*.json frontend/ | |
| RUN cd frontend && npm ci | |
| # Se necesita el HTML/CSS de la raíz y los datos para la build. | |
| COPY frontend/ frontend/ | |
| COPY index.html ./ | |
| COPY css/ css/ | |
| COPY assets/ assets/ | |
| COPY data/ data/ | |
| RUN cd frontend && npm run build # emite /build/dist | |
| # ---------- Stage 2: runtime backend (FastAPI + uv) ---------- | |
| FROM python:3.12-slim AS runtime | |
| # uv desde su imagen oficial (rápido y reproducible). | |
| COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ | |
| WORKDIR /app | |
| # Instala dependencias con el lockfile (capa cacheable). RAG viene ACTIVADO por defecto: con | |
| # WITH_RAG=0 el retriever degrada en silencio y las interpretaciones salen sin fundamentar, que | |
| # es justo el fallo que no queremos que pase inadvertido en producción. | |
| ARG WITH_RAG=1 | |
| COPY backend/pyproject.toml backend/uv.lock* backend/ | |
| RUN cd backend && if [ "$WITH_RAG" = "1" ]; then \ | |
| uv sync --frozen --no-dev --group rag; \ | |
| else \ | |
| uv sync --frozen --no-dev; \ | |
| fi | |
| # Código del backend | |
| COPY backend/ backend/ | |
| # Estáticos públicos: build del frontend + datos de referencia (servidos en /data) | |
| COPY --from=frontend /build/dist ./dist | |
| COPY data/ ./data/ | |
| # Índice RAG horneado de sólo lectura. Vive en instance/ (fuera del webroot); no requiere | |
| # almacenamiento persistente. La BD de usuarios (instance/*.db) queda EXCLUIDA vía | |
| # .dockerignore: nunca se hornea. | |
| # | |
| # Dos vías: si instance/rag_index existe en el contexto de build se copia; si no (clon limpio, | |
| # CI), se descarga del dataset PRIVADO del Hub con HF_TOKEN pasado como secreto de build. | |
| COPY instance* ./instance/ | |
| # Se usa la API de Python y NO el CLI `hf`: en huggingface_hub 1.16.1 el CLI devuelve código 1 | |
| # aunque la operación vaya bien (incompatibilidad typer/click), lo que abortaría la build. | |
| ARG HF_INDEX_REPO=blackmistcode/morphos-rag-index | |
| RUN --mount=type=secret,id=hf_token,required=false \ | |
| if [ "$WITH_RAG" = "1" ] && [ ! -d /app/instance/rag_index ]; then \ | |
| if [ -f /run/secrets/hf_token ]; then \ | |
| HF_TOKEN="$(cat /run/secrets/hf_token)" backend/.venv/bin/python -c "\ | |
| from huggingface_hub import snapshot_download; \ | |
| snapshot_download(repo_id='$HF_INDEX_REPO', repo_type='dataset', local_dir='/app/instance/rag_index')"; \ | |
| else \ | |
| echo "AVISO: sin instance/rag_index y sin secreto hf_token; la imagen quedará SIN RAG." >&2; \ | |
| fi; \ | |
| fi | |
| # Modelos de embeddings y reranking horneados en la imagen (~6.4 GB). Sin esto, la primera | |
| # petición intentaría descargarlos de huggingface.co en caliente: lento en CPU-basic y, si la | |
| # red falla, el reranker cae en silencio a orden RRF. Con HF_HUB_OFFLINE=1 en runtime, una | |
| # descarga que faltase falla de forma ruidosa en vez de degradar sin avisar. | |
| # | |
| # Se monta el mismo secreto que en la descarga del índice: los dos modelos son públicos y la | |
| # descarga funciona sin credencial, pero anónima cae en el límite de tasa del Hub — y son 6.4 GB, | |
| # justo el caso donde eso muerde. `required=false` mantiene la build funcionando sin el secreto. | |
| # El montaje sólo existe durante este RUN: el token no queda en ninguna capa de la imagen. | |
| ARG RAG_EMBED_MODEL=BAAI/bge-m3 | |
| ARG RAG_RERANKER_MODEL=BAAI/bge-reranker-v2-m3 | |
| ENV HF_HOME=/opt/hf | |
| RUN --mount=type=secret,id=hf_token,required=false \ | |
| if [ "$WITH_RAG" = "1" ]; then \ | |
| if [ -f /run/secrets/hf_token ]; then \ | |
| HF_TOKEN="$(cat /run/secrets/hf_token)"; export HF_TOKEN; \ | |
| fi; \ | |
| backend/.venv/bin/python -c "\ | |
| from sentence_transformers import SentenceTransformer, CrossEncoder; \ | |
| SentenceTransformer('$RAG_EMBED_MODEL'); CrossEncoder('$RAG_RERANKER_MODEL')"; \ | |
| fi | |
| ENV MORPHOS_ENTORNO=prod \ | |
| MORPHOS_COOKIE_SECURE=true \ | |
| HF_HOME=/opt/hf \ | |
| HF_HUB_OFFLINE=1 \ | |
| PATH="/app/backend/.venv/bin:$PATH" | |
| # HF Spaces expone el 7860. | |
| EXPOSE 7860 | |
| COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh | |
| RUN chmod +x /usr/local/bin/docker-entrypoint.sh | |
| ENTRYPOINT ["docker-entrypoint.sh"] | |
| CMD ["uvicorn", "app.main:app", "--app-dir", "backend", "--host", "0.0.0.0", "--port", "7860"] | |