Spaces:
Running
Running
| # Dockerfile — Picarones | |
| # Image Docker multi-étape avec Tesseract OCR pré-installé | |
| # | |
| # Usage : | |
| # docker build -t picarones:latest . | |
| # docker run -p 7860:7860 picarones:latest | |
| # docker run -p 7860:7860 -v $(pwd)/corpus:/app/corpus picarones:latest | |
| # | |
| # Variables d'environnement supportées : | |
| # OPENAI_API_KEY, ANTHROPIC_API_KEY, MISTRAL_API_KEY | |
| # GOOGLE_APPLICATION_CREDENTIALS | |
| # AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION | |
| # AZURE_DOC_INTEL_ENDPOINT, AZURE_DOC_INTEL_KEY | |
| # ────────────────────────────────────────────────────────────────── | |
| # Étape 1 : builder — installe les dépendances Python dans un venv | |
| # ────────────────────────────────────────────────────────────────── | |
| # ────────────────────────────────────────────────────────────────── | |
| # Sprint A8 (M-2) + Sprint A16 (build déterministe) — image de base | |
| # épinglée à la fois par tag (lisibilité humaine) et par digest sha256 | |
| # (reproductibilité bit-à-bit). | |
| # | |
| # Pourquoi le digest : ``python:3.11.13-slim`` peut être re-publié au | |
| # fil des patches Debian avec un même tag mais un contenu différent. | |
| # Pour la reproductibilité institutionnelle BnF, ``@sha256:...`` fige | |
| # l'image binaire — deux ``docker build`` séparés produisent une | |
| # couche de base identique octet par octet. | |
| # | |
| # Rotation trimestrielle (avant chaque release majeure) : | |
| # | |
| # TOKEN=$(curl -s "https://auth.docker.io/token?\ | |
| # service=registry.docker.io&scope=repository:library/python:pull" \ | |
| # | jq -r .token) | |
| # curl -sI -H "Authorization: Bearer $TOKEN" \ | |
| # -H "Accept: application/vnd.oci.image.index.v1+json" \ | |
| # https://registry-1.docker.io/v2/library/python/manifests/3.11.13-slim \ | |
| # | grep -i docker-content-digest | |
| # # → mettre à jour le digest ci-dessous + bumper PYTHON_BASE_IMAGE | |
| # | |
| # La forme ``image:tag@sha256:...`` est documentée par Docker comme | |
| # valide ; les machines de développement sans registry proxy peuvent | |
| # pull aussi bien que par tag — le digest étant immuable, le pull | |
| # est strictement équivalent à un pull par tag actuel. | |
| # ────────────────────────────────────────────────────────────────── | |
| ARG PYTHON_BASE_IMAGE=python:3.11.13-slim@sha256:9bffe4353b925a1656688797ebc68f9c525e79b1d377a764d232182a519eeec4 | |
| FROM ${PYTHON_BASE_IMAGE} AS builder | |
| WORKDIR /app | |
| # Sprint A14 (correctif suite scan Trivy CI) — applique en priorité les | |
| # patches Debian disponibles AVANT d'installer build-essential/git, pour | |
| # éviter d'embarquer les CVE de la base image (libssl3t64, libc6, etc.). | |
| # | |
| # ``--fix-missing`` + ``Acquire::Retries=3`` : tolère la rotation du pool | |
| # debian-security. Quand Debian publie un point-release, l'ancien .deb | |
| # est émondé du pool quasi immédiatement alors que l'index fraîchement | |
| # récupéré le référence encore → 404 transitoire sur un paquet hors | |
| # scope (ex. linux-libc-dev, en-têtes noyau inutiles au runtime). On | |
| # saute ce paquet au lieu de casser le build ; tous les patches CVE | |
| # téléchargeables (libssl3t64, libc6, openssl, zlib1g…) sont appliqués. | |
| RUN apt-get update -o Acquire::Retries=3 && \ | |
| apt-get upgrade -y --fix-missing -o Acquire::Retries=3 && \ | |
| apt-get install -y --no-install-recommends -o Acquire::Retries=3 \ | |
| build-essential \ | |
| git && \ | |
| apt-get clean && \ | |
| rm -rf /var/lib/apt/lists/* | |
| # Copier les fichiers de configuration du package + lock file Docker. | |
| # ``requirements-docker.lock`` (Sprint A16) gèle l'arbre de dépendances | |
| # transitif résolu par ``uv pip compile pyproject.toml --extra web --extra llm``. | |
| COPY pyproject.toml . | |
| COPY README.md . | |
| COPY requirements-docker.lock . | |
| COPY picarones/ picarones/ | |
| # Crée le venv isolé /opt/venv et l'active pour les ``RUN`` suivants. | |
| # Le runtime fera ``COPY --from=builder /opt/venv /opt/venv`` ; sans cette | |
| # création explicite le COPY échoue (régression remontée par CI A14). | |
| RUN python -m venv /opt/venv | |
| ENV PATH="/opt/venv/bin:$PATH" | |
| # Sprint A16 : installation déterministe via lock file. | |
| # | |
| # 1. Patch pip/setuptools/wheel (Trivy scanne /opt/venv/lib/python3.11/ | |
| # site-packages — sans patch les CVE setuptools/wheel ressortent). | |
| # 2. ``--no-deps`` sur le lock empêche pip de re-résoudre — l'arbre | |
| # pinné par ``uv pip compile`` est complet, transitives incluses. | |
| # 3. ``--no-deps`` sur picarones lui-même : le lock contient déjà | |
| # toutes ses dépendances ; cette ligne installe juste le code. | |
| RUN pip install --upgrade --no-cache-dir \ | |
| "pip>=24.2" "setuptools>=78.1.1" "wheel>=0.46.2" && \ | |
| pip install --no-cache-dir --no-deps -r requirements-docker.lock && \ | |
| pip install --no-cache-dir --no-deps -e . && \ | |
| pip cache purge | |
| # Patch également la copie système de pip/setuptools/wheel (hors venv) | |
| # que Trivy détecte via ``/usr/local/lib/python3.11/site-packages`` — | |
| # subsiste dans l'image runtime même quand le venv est utilisé. | |
| RUN /usr/local/bin/pip install --upgrade --no-cache-dir \ | |
| "pip>=24.2" "setuptools>=78.1.1" "wheel>=0.46.2" | |
| # ────────────────────────────────────────────────────────────────── | |
| # Étape 2 : runtime — image finale légère avec Tesseract | |
| # ────────────────────────────────────────────────────────────────── | |
| # ARG redéclaré ici car les variables ARG hors ``FROM`` sont scopées | |
| # par étape ; sans cette redéclaration le ``FROM`` du runtime perd | |
| # l'épinglage du builder. La valeur DOIT correspondre à celle de | |
| # l'étape builder (digest inclus) — sinon les couches OS divergent. | |
| ARG PYTHON_BASE_IMAGE=python:3.11.13-slim@sha256:9bffe4353b925a1656688797ebc68f9c525e79b1d377a764d232182a519eeec4 | |
| FROM ${PYTHON_BASE_IMAGE} AS runtime | |
| # Version injectée au build (``--build-arg PICARONES_VERSION=…``) ; | |
| # défaut ``dev`` plutôt qu'un ``1.0.0`` figé qui dérive de la version | |
| # réelle (setuptools-scm). Le Makefile la dérive automatiquement. | |
| ARG PICARONES_VERSION=dev | |
| LABEL description="Picarones — Plateforme de comparaison de moteurs OCR pour documents patrimoniaux" | |
| LABEL org.opencontainers.image.version="${PICARONES_VERSION}" | |
| LABEL org.opencontainers.image.source="https://github.com/maribakulj/Picarones" | |
| LABEL org.opencontainers.image.licenses="Apache-2.0" | |
| WORKDIR /app | |
| # ── Dépendances système ───────────────────────────────────────── | |
| # Sprint A14 (correctif Trivy) : ``apt-get upgrade -y`` avant install | |
| # pour récupérer les patches de sécurité Debian (libssl3t64, libc6, | |
| # openssl, etc.) — la base image Python ne les inclut pas par défaut. | |
| # ``--fix-missing`` + ``Acquire::Retries=3`` : même résilience à la | |
| # rotation du pool debian-security que l'étape builder (cf. supra). | |
| # | |
| # Sprint S6.1 — reproductibilité institutionnelle (BnF) : | |
| # | |
| # ``tesseract-ocr`` n'est PAS pinné à une version exacte (ex : | |
| # ``=5.3.0-2``) car Debian point-release rebump fréquemment : | |
| # ``5.3.0-2`` → ``5.3.0-2+deb12u1`` → ``5.3.4-1``. Un pin exact | |
| # casse le build dès que la version disparaît du miroir. | |
| # | |
| # Le contrat de reproductibilité repose plutôt sur : | |
| # | |
| # 1. La base image Python pinée par digest SHA256 (cf. ``ARG | |
| # PYTHON_BASE_IMAGE`` ci-dessus) — Debian bookworm garantit la | |
| # stabilité ABI au sein du même point-release. | |
| # 2. ``requirements-docker.lock`` qui fige les versions Python. | |
| # 3. Le ``RunManifest.dependencies_lock`` capture la version | |
| # Tesseract effective au runtime (``tesseract --version``) | |
| # pour traçabilité scientifique. | |
| # | |
| # Si une version mineure de Tesseract introduit une régression | |
| # CER, le mainteneur peut pinner explicitement ICI à ce moment-là | |
| # (avec une note CHANGELOG). | |
| RUN apt-get update -o Acquire::Retries=3 && \ | |
| apt-get upgrade -y --fix-missing -o Acquire::Retries=3 && \ | |
| apt-get install -y --no-install-recommends -o Acquire::Retries=3 \ | |
| # Tesseract OCR 5 + modèles de langues (Debian bookworm). | |
| tesseract-ocr \ | |
| tesseract-ocr-fra \ | |
| tesseract-ocr-lat \ | |
| tesseract-ocr-eng \ | |
| tesseract-ocr-deu \ | |
| tesseract-ocr-ita \ | |
| tesseract-ocr-spa \ | |
| # Bibliothèques image pour Pillow | |
| libpng16-16 \ | |
| libjpeg62-turbo \ | |
| libtiff6 \ | |
| libwebp7 \ | |
| # Utilitaires | |
| curl && \ | |
| apt-get clean && \ | |
| rm -rf /var/lib/apt/lists/* | |
| # ── Vérification fail-fast de Tesseract (incident 2026-05-16) ─── | |
| # ``apt-get upgrade --fix-missing`` (résilience à la rotation du pool | |
| # debian-security, commit d5d68ae) peut laisser un jeu de libs | |
| # runtime INCOHÉRENT si un .deb co-dépendant est sauté : le binaire | |
| # ``tesseract`` se fige alors à la reconnaissance (deadlock OpenMP / | |
| # mismatch ABI liblept/libstdc++) et le run prod timeoute sur CHAQUE | |
| # document — sans que le build n'ait rien signalé. | |
| # | |
| # On exige donc, au build : (a) que Tesseract charge ses libs et | |
| # expose la langue ``fra``, (b) qu'une reconnaissance réelle se | |
| # termine sous 30 s. Un Tesseract cassé OU figé casse désormais le | |
| # BUILD (le Space conserve l'image précédente fonctionnelle) au lieu | |
| # de déployer une image qui gèle en silence. ``--fix-missing`` est | |
| # conservé : l'intention CVE de d5d68ae n'est pas régressée, seule | |
| # la défaillance silencieuse est convertie en échec bruyant. | |
| RUN set -eu; \ | |
| timeout 30 tesseract --version; \ | |
| timeout 30 tesseract --list-langs 2>&1 | grep -qx fra; \ | |
| printf 'P1\n16 16\n' > /tmp/tess_smoke.pbm; \ | |
| i=0; while [ "$i" -lt 16 ]; do \ | |
| printf '1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0\n' >> /tmp/tess_smoke.pbm; \ | |
| i=$((i + 1)); \ | |
| done; \ | |
| rc=0; \ | |
| timeout 30 tesseract /tmp/tess_smoke.pbm - -l fra --psm 6 \ | |
| > /dev/null 2>&1 || rc=$?; \ | |
| rm -f /tmp/tess_smoke.pbm; \ | |
| if [ "$rc" = 124 ]; then \ | |
| echo "FATAL: tesseract a gelé (timeout reconnaissance) — \ | |
| build refusé, libs runtime probablement incohérentes." >&2; \ | |
| exit 1; \ | |
| fi | |
| # Patch pip/setuptools/wheel système du runtime (en dehors du venv). | |
| # Trivy scanne /usr/local/lib/python3.11/site-packages indépendamment. | |
| RUN /usr/local/bin/pip install --upgrade --no-cache-dir \ | |
| "pip>=24.2" "setuptools>=78.1.1" "wheel>=0.46.2" | |
| # ── Venv Python depuis le builder ────────────────────────────── | |
| COPY --from=builder /opt/venv /opt/venv | |
| ENV PATH="/opt/venv/bin:$PATH" | |
| # ── Code source de l'application ─────────────────────────────── | |
| COPY --from=builder /app /app | |
| # ── Répertoires de données ────────────────────────────────────── | |
| RUN mkdir -p /app/corpus /app/rapports /app/data | |
| # ── Utilisateur non-root pour la sécurité ────────────────────── | |
| RUN useradd -m -u 1000 picarones && \ | |
| chown -R picarones:picarones /app /opt/venv | |
| USER picarones | |
| # ── Variables d'environnement par défaut ─────────────────────── | |
| ENV PYTHONUNBUFFERED=1 | |
| ENV PYTHONIOENCODING=utf-8 | |
| ENV TESSDATA_PREFIX=/usr/share/tesseract-ocr/5/tessdata | |
| # Tesseract LSTM (oem=3) parallélise via OpenMP. Sur le Space HF | |
| # (~2 vCPU partagés) l'OpenMP non borné suroccupe le CPU (N threads | |
| # pour 2 cœurs) → OCR plus LENT et instable. Le forcer mono-thread | |
| # par appel est ici PLUS rapide et déterministe (le parallélisme | |
| # inter-documents est déjà géré par le ThreadPool du CorpusRunner). | |
| ENV OMP_THREAD_LIMIT=1 | |
| # ── Ports ─────────────────────────────────────────────────────── | |
| EXPOSE 7860 | |
| # ── Health check ──────────────────────────────────────────────── | |
| HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \ | |
| CMD curl -f http://localhost:7860/health || exit 1 | |
| # ── Démarrage ─────────────────────────────────────────────────── | |
| CMD ["picarones", "serve", "--host", "0.0.0.0", "--port", "7860"] | |