From f051d7bb5b94315a610834536cadb46c872d66a3 Mon Sep 17 00:00:00 2001 From: Pier-Jean Malandrino Date: Wed, 6 May 2026 09:55:46 +0200 Subject: [PATCH] chore(#254): slim latest-local image (multi-stage + opt-in reasoning) - Move docling-agent + mellea out of requirements.txt into a dedicated requirements-reasoning.txt. They are no longer pulled into latest-remote (regression fix) nor into latest-local by default. - Dockerfile: split into builder-remote / builder-local / runtime-base stages. The runtime image carries no pip and no build cache; the source is COPYed only in the final stages so a code-only change reuses every pip-install layer. - Add WITH_REASONING build-arg (default false) on the local target. Set to true to bundle the R&D reasoning-trace deps for a local-reasoning image. - Harden .dockerignore: tests/, data/, uploads/, IDE files, stray node_modules / package-lock.json, the one-shot migrate_06.py utility. - Set HF_HOME so the Docling/HF model cache lands in a deterministic appuser-owned path that compose can mount as a volume. --- document-parser/.dockerignore | 22 +++++ document-parser/Dockerfile | 105 +++++++++++++++------ document-parser/requirements-reasoning.txt | 11 +++ document-parser/requirements.txt | 6 -- 4 files changed, 108 insertions(+), 36 deletions(-) create mode 100644 document-parser/requirements-reasoning.txt diff --git a/document-parser/.dockerignore b/document-parser/.dockerignore index 643c3a7..5de5985 100644 --- a/document-parser/.dockerignore +++ b/document-parser/.dockerignore @@ -10,3 +10,25 @@ __pycache__/ .mypy_cache/ .pytest_cache/ .ruff_cache/ + +# Test + dev assets — never needed in the runtime image. +tests/ +conftest.py +pytest.ini +requirements-test.txt + +# Local runtime data — must not leak into the image. +data/ +uploads/ + +# One-shot migration utility — runs out-of-band, not from the image. +tools/migrate_06.py + +# IDE / editor metadata. +*.iml +.idea/ +.vscode/ + +# Stray frontend artefacts that have no business inside document-parser/. +package-lock.json +node_modules/ diff --git a/document-parser/Dockerfile b/document-parser/Dockerfile index 3c21f9a..3d0597d 100644 --- a/document-parser/Dockerfile +++ b/document-parser/Dockerfile @@ -1,56 +1,101 @@ # syntax=docker/dockerfile:1 # ============================================================================= -# Docling Studio — backend image (multi-target: remote / local) +# Docling Studio — backend image (multi-stage, multi-target: remote / local) # -# Usage: +# Standard usage: # docker build --target remote -t docling-studio-backend:remote . # docker build --target local -t docling-studio-backend:local . +# +# R&D variant — opt in to the reasoning-trace runner (docling-agent + mellea, +# heavy transitive deps; runtime-gated by REASONING_ENABLED): +# docker build --target local --build-arg WITH_REASONING=true \ +# -t docling-studio-backend:local-reasoning . +# +# Cache notes: +# - Source is COPYed only in the final stages, never in the builders. +# A code-only change reuses every pip-install layer. +# - Each builder owns a venv at /opt/venv that the final stage copies in +# wholesale (no pip in the runtime image). # ============================================================================= -# --- Base: common deps for both targets --- -FROM python:3.12-slim AS base +# --- Builder: remote (lightweight HTTP-only deps) ---------------------------- +FROM python:3.12-slim AS builder-remote + +ENV PIP_NO_CACHE_DIR=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 + +WORKDIR /build +RUN python -m venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" + +COPY requirements.txt . +RUN pip install -r requirements.txt + +# --- Builder: local (torch CPU + full Docling, optional reasoning deps) ------ +FROM python:3.12-slim AS builder-local + +ENV PIP_NO_CACHE_DIR=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 + +WORKDIR /build +RUN python -m venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" + +COPY requirements.txt requirements-local.txt requirements-reasoning.txt ./ + +# torch CPU wheels in their own layer — pinned to the CPU-only index so the +# transitive resolution of docling does not re-pull a CUDA build. +RUN pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu + +# Full local stack (requirements-local.txt re-includes requirements.txt). +RUN pip install -r requirements-local.txt + +# Reasoning is opt-in; off by default keeps the standard image lean. +ARG WITH_REASONING=false +RUN if [ "$WITH_REASONING" = "true" ]; then \ + pip install -r requirements-reasoning.txt; \ + fi + +# --- Runtime base (no pip, no source — shared by both final targets) --------- +FROM python:3.12-slim AS runtime-base RUN apt-get update && apt-get install -y --no-install-recommends \ - poppler-utils \ + poppler-utils \ && rm -rf /var/lib/apt/lists/* +RUN useradd --create-home --shell /bin/bash appuser \ + && mkdir -p /app/uploads /app/data /home/appuser/.cache/huggingface \ + && chown -R appuser:appuser /app /home/appuser/.cache + WORKDIR /app -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -COPY . . - -RUN useradd --create-home --shell /bin/bash appuser \ - && mkdir -p /app/uploads /app/data \ - && chown -R appuser:appuser /app +ENV PATH="/opt/venv/bin:$PATH" \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + UPLOAD_DIR=/app/uploads \ + DB_PATH=/app/data/docling_studio.db \ + HF_HOME=/home/appuser/.cache/huggingface EXPOSE 8000 - -ENV UPLOAD_DIR=/app/uploads -ENV DB_PATH=/app/data/docling_studio.db - USER appuser CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] -# --- Remote: lightweight, delegates to Docling Serve --- -FROM base AS remote +# --- Final: remote ----------------------------------------------------------- +FROM runtime-base AS remote +COPY --from=builder-remote --chown=appuser:appuser /opt/venv /opt/venv +COPY --chown=appuser:appuser . /app ENV CONVERSION_ENGINE=remote -# --- Local: full Docling in-process --- -FROM base AS local +# --- Final: local ------------------------------------------------------------ +FROM runtime-base AS local USER root RUN apt-get update && apt-get install -y --no-install-recommends \ - libgl1 \ - libglib2.0-0 \ + libgl1 \ + libglib2.0-0 \ && rm -rf /var/lib/apt/lists/* - -COPY requirements-local.txt . -RUN pip install --no-cache-dir torch torchvision --index-url https://download.pytorch.org/whl/cpu \ - && pip install --no-cache-dir -r requirements-local.txt - -RUN chown -R appuser:appuser /app \ - && chown -R appuser:appuser /usr/local/lib/python3.12/site-packages/rapidocr/models USER appuser + +COPY --from=builder-local --chown=appuser:appuser /opt/venv /opt/venv +COPY --chown=appuser:appuser . /app ENV CONVERSION_ENGINE=local diff --git a/document-parser/requirements-reasoning.txt b/document-parser/requirements-reasoning.txt new file mode 100644 index 0000000..381b791 --- /dev/null +++ b/document-parser/requirements-reasoning.txt @@ -0,0 +1,11 @@ +# R&D reasoning-trace live runner — calls docling-agent's `_rag_loop` over +# an Ollama backend. Gated server-side by `REASONING_ENABLED`; pulls a +# heavy transitive set (mellea + pydantic-ai + several LLM SDKs). +# +# Opt in at build time with `--build-arg WITH_REASONING=true` on the +# `local` Dockerfile target. Default off keeps the standard image lean. +# +# See https://github.com/docling-project/docling-agent/issues/26 for the +# public-API replacement of `_rag_loop`. +docling-agent==0.1.0 +mellea==0.4.2 diff --git a/document-parser/requirements.txt b/document-parser/requirements.txt index 5f55516..1137357 100644 --- a/document-parser/requirements.txt +++ b/document-parser/requirements.txt @@ -9,9 +9,3 @@ httpx>=0.27.0,<1.0.0 pypdfium2>=4.0.0,<5.0.0 opensearch-py[async]>=2.6.0,<3.0.0 neo4j>=5.15.0,<6.0.0 -# R&D reasoning-trace live runner — calls docling-agent's `_rag_loop` over -# an Ollama backend. Gated server-side by `REASONING_ENABLED`; pulls ~60MB -# of deps. See https://github.com/docling-project/docling-agent/issues/26 for -# the public-API replacement of `_rag_loop`. -docling-agent==0.1.0 -mellea==0.4.2