Spaces:
Sleeping
Sleeping
| # Build stage — compile dependencies | |
| FROM python:3.11-slim AS builder | |
| WORKDIR /build | |
| # Install build tools | |
| RUN apt-get update && apt-get install -y \ | |
| libpq-dev \ | |
| gcc \ | |
| && rm -rf /var/lib/apt/lists/* | |
| RUN python -m venv /opt/venv | |
| ENV PATH="/opt/venv/bin:$PATH" | |
| # Install PyTorch CPU-only first (smaller than GPU version) | |
| RUN pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cpu | |
| COPY requirements.txt . | |
| RUN pip install --no-cache-dir -r requirements.txt | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Runtime stage — minimal image | |
| FROM python:3.11-slim AS runtime | |
| # Install only runtime system libraries. | |
| # tesseract-ocr — OCR engine for scanned documents (pytesseract wraps it) | |
| # poppler-utils — pdftoppm, used by pdf2image to rasterise pages for OCR | |
| # Without these, ingestion still works; scanned pages just yield no text. | |
| RUN apt-get update && apt-get install -y --no-install-recommends \ | |
| libpq5 \ | |
| libmagic1 \ | |
| curl \ | |
| tesseract-ocr \ | |
| tesseract-ocr-eng \ | |
| poppler-utils \ | |
| && rm -rf /var/lib/apt/lists/* | |
| WORKDIR /app | |
| # Copy installed packages from builder stage | |
| COPY --from=builder /opt/venv /opt/venv | |
| # Ensure scripts in venv are usable | |
| ENV PATH="/opt/venv/bin:$PATH" | |
| # sentence-transformers config: use CPU, limit memory | |
| ENV TOKENIZERS_PARALLELISM=false | |
| ENV TRANSFORMERS_CACHE=/app/.cache/huggingface | |
| ENV SENTENCE_TRANSFORMERS_HOME=/app/.cache/sentence-transformers | |
| # Create non-root user for security | |
| RUN groupadd --gid 1001 appgroup \ | |
| && useradd --uid 1001 --gid appgroup --shell /bin/sh --create-home appuser | |
| # Create cache and upload directories. | |
| # | |
| # /app/uploads must exist and be writable *before* the app runs. WORKDIR | |
| # creates /app owned by root, and `COPY --chown` only changes the ownership of | |
| # the files copied into it — not the directory itself. So appuser could read | |
| # /app but not create entries in it, and the upload endpoint died with: | |
| # | |
| # PermissionError: [Errno 13] Permission denied: '/app/uploads' | |
| # | |
| # from os.makedirs() on the very first upload. Creating it here, and taking | |
| # ownership of /app itself, fixes that. | |
| RUN mkdir -p /app/.cache/huggingface /app/.cache/sentence-transformers /app/uploads \ | |
| && chown -R appuser:appgroup /app | |
| # Copy application code | |
| COPY --chown=appuser:appgroup . . | |
| # The entrypoint must be executable regardless of the checkout's file mode, | |
| # which is not preserved on Windows clones. | |
| RUN chmod +x /app/entrypoint.sh | |
| # Re-assert ownership: the COPY above can reintroduce root-owned paths, and | |
| # /app must stay writable for uploads. | |
| RUN chown -R appuser:appgroup /app | |
| # Switch to non-root user | |
| USER appuser | |
| # Expose application port | |
| EXPOSE 8000 | |
| # Health check for Docker and orchestrators | |
| HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \ | |
| CMD curl -f http://localhost:8000/health || exit 1 | |
| # Migrations run first, then the server — see entrypoint.sh. | |
| # Worker count is overridable via GUNICORN_WORKERS; each worker loads its own | |
| # copy of the cross-encoder, so raising it costs roughly 400MB apiece. | |
| CMD ["./entrypoint.sh"] | |