File size: 2,494 Bytes
b2931f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a8bc5d0
 
 
b2931f4
 
 
 
a8bc5d0
b2931f4
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# FinRAG backend image β€” portable across hosts (Hugging Face Spaces, Render,
# Cloud Run, local). Build context = repo ROOT (it needs backend/ + data/):
#   docker build -t finrag-api .
#
# Image layout mirrors the dev tree so REPO_ROOT resolves identically:
#   config.py at /app/backend/src/finrag/config.py β†’ parents[3] == /app
#   β†’ DuckDB + BM25 (REPO_ROOT/data/...) live at /app/data. WORKDIR=/app keeps
#   settings.duckdb_path ("./data/...") valid too.
#
# Port: listens on $PORT if the host injects one (Render/Cloud Run), else 8000.
# On Hugging Face Spaces, declare `app_port: 8000` in the Space README frontmatter.

# ---- builder: resolve + install deps into a venv (no ingestion/dev groups) ----
FROM python:3.11-slim AS builder
COPY --from=ghcr.io/astral-sh/uv:0.11.15 /uv /uvx /bin/
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
WORKDIR /app/backend
COPY backend/pyproject.toml backend/uv.lock ./
RUN uv sync --frozen --no-install-project --no-dev
COPY backend/ ./
RUN uv sync --frozen --no-dev

# ---- runtime: slim, non-root ----
FROM python:3.11-slim AS runtime
# libgomp1 covers numpy/rank-bm25's OpenMP dependency on slim.
RUN apt-get update && apt-get install -y --no-install-recommends libgomp1 \
    && rm -rf /var/lib/apt/lists/*
RUN useradd -m -u 1000 appuser
WORKDIR /app

# Installed venv + editable project source (finrag β†’ backend/src), then the two
# runtime data artifacts (Qdrant vectors live in the cloud cluster, not here).
COPY --from=builder --chown=appuser:appuser /app/backend /app/backend
COPY --chown=appuser:appuser data/duckdb /app/data/duckdb
COPY --chown=appuser:appuser data/bm25_index.pkl /app/data/bm25_index.pkl
# Embedded Qdrant vector store β€” baked in like DuckDB/BM25 so the image is fully
# self-contained (no external cluster to idle-wipe, no QDRANT_URL/KEY secret).
COPY --chown=appuser:appuser data/qdrant /app/data/qdrant

ENV PATH="/app/backend/.venv/bin:$PATH" \
    PYTHONUNBUFFERED=1 \
    PYTHONIOENCODING=utf-8 \
    QDRANT_PATH=./data/qdrant \
    PORT=8000
USER appuser
EXPOSE 8000

# Healthcheck honors $PORT so it stays correct on any host.
HEALTHCHECK --interval=30s --timeout=5s --start-period=25s --retries=3 \
  CMD python -c "import os,sys,urllib.request; p=os.environ.get('PORT','8000'); sys.exit(0 if urllib.request.urlopen(f'http://localhost:{p}/health',timeout=3).status==200 else 1)"

# Shell form so ${PORT} expands at runtime.
CMD ["sh", "-c", "uvicorn finrag.main:app --host 0.0.0.0 --port ${PORT:-8000}"]