# syntax=docker/dockerfile:1.7 # --------------------------------------------------------------------------- # Production worker image — multi-stage build. # Keeps the OCR system packages (tesseract, poppler, libgl1) in runtime; # drops build-essential and the [dev] python extras. # --------------------------------------------------------------------------- FROM python:3.12-slim AS builder ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 WORKDIR /build RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ && rm -rf /var/lib/apt/lists/* RUN python -m venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" COPY pyproject.toml README.md ./ RUN mkdir -p app && touch app/__init__.py \ && pip install --upgrade pip wheel setuptools \ && pip install . \ && (pip install ".[worker]" || \ (echo "WARN: worker extras failed; installing fallbacks" \ && pip install pytesseract pdf2image Pillow numpy)) # --------------------------------------------------------------------------- FROM python:3.12-slim AS runtime ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ PYTHONPATH=/app \ PATH="/opt/venv/bin:$PATH" \ HF_HOME=/app/.cache/huggingface \ XDG_CACHE_HOME=/app/.cache # Runtime-only OCR/video system packages (no build-essential). RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ tesseract-ocr \ tesseract-ocr-eng \ poppler-utils \ libgl1 \ libglib2.0-0 \ ffmpeg \ && rm -rf /var/lib/apt/lists/* \ && addgroup --system --gid 1001 app \ && adduser --system --uid 1001 --ingroup app --no-create-home app WORKDIR /app # Self-contained venv from the builder stage. COPY --from=builder /opt/venv /opt/venv # Pre-download the three retrieval models (embedder, reranker, BM25 sparse). # Retry on HF 429s; FAIL the build if retries exhaust so the CI error is # loud instead of producing an image whose worker can't embed/rerank. RUN mkdir -p /app/.cache/huggingface && \ for i in 1 2 3 4 5; do \ if python -c "\ from sentence_transformers import SentenceTransformer, CrossEncoder; \ from fastembed import SparseTextEmbedding; \ SentenceTransformer('BAAI/bge-small-en-v1.5'); \ CrossEncoder('BAAI/bge-reranker-base'); \ list(SparseTextEmbedding(model_name='Qdrant/bm25').embed(['warmup']))\ "; then \ break; \ fi; \ if [ "$i" = "5" ]; then \ echo "FATAL: HF model download failed after 5 attempts"; exit 1; \ fi; \ echo "HF download attempt $i failed; sleeping $((i*15))s"; \ sleep $((i*15)); \ done COPY app ./app COPY alembic ./alembic COPY alembic.ini ./alembic.ini RUN chown -R app:app /app USER app EXPOSE 7860 CMD ["python", "-m", "app.workers.main"]