File size: 4,886 Bytes
a2907ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# Image **moteur** Cinoc pour un Hugging Face Space (SDK: docker).
#
# ≠ la vitrine lecture seule (deploy/Dockerfile{,.space}) : celle-ci **bake un vrai
# moteur OCR gratuit** (Tesseract 5 + langues fra/lat/eng) pour qu'un visiteur du
# Space public lance un run Tesseract **sans clé ni installation**. Les moteurs
# cloud et les plugins tiers restent *gated* (mode public, fail-closed côté appli).
#
# Contexte de build « racine plate » (Dockerfile / requirements.txt / pyproject.toml
# / cinoc / reports TOUS à la racine) — assemblé par .github/workflows/deploy-space.yml,
# comme Dockerfile.space.
FROM python:3.11-slim

# ── Dépendances système : Tesseract 5 + langues + libs image (Pillow) ──────────
# Les langues sont baquées (pas téléchargées au runtime) : un Space free-tier n'a
# pas d'accès réseau garanti au démarrage, et le déterminisme exige une version de
# binaire figée. ``fra`` est le besoin patrimonial central ; ``lat`` (latin) et
# ``eng`` complètent. Pas de pin de version exacte (Debian point-release rebump
# fréquemment ``5.3.0-2`` → ``+deb12u1`` → casse le build) : la repro repose sur la
# capture de ``tesseract --version`` dans ``RunManifest.system_binaries_lock``.
RUN apt-get update -o Acquire::Retries=3 \
    && apt-get install -y --no-install-recommends -o Acquire::Retries=3 \
        tesseract-ocr \
        tesseract-ocr-fra \
        tesseract-ocr-lat \
        tesseract-ocr-eng \
        libtiff6 \
        libjpeg62-turbo \
        libpng16-16 \
        libwebp7 \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# ── Variables d'exécution ──────────────────────────────────────────────────────
# TESSDATA_PREFIX : emplacement Debian bookworm des modèles Tesseract 5.
ENV TESSDATA_PREFIX=/usr/share/tesseract-ocr/5/tessdata
# OMP_THREAD_LIMIT=1 : OBLIGATOIRE sur free-tier 2 vCPU. Tesseract LSTM (oem 3)
# parallélise via OpenMP ; non borné sur un CPU partagé il **deadlock** (incident
# documenté côté Picarones, 2026-05-16) — l'OCR gèle et chaque document timeoute.
# Mono-thread par appel = plus rapide ET déterministe ici.
ENV OMP_THREAD_LIMIT=1
# Mode public (verrou *fail-closed*) **non forcé** : par défaut l'instance exécute
# ses moteurs (cloud compris) avec la clé de l'opérateur — pas de 403. Qui expose
# un Space **public** et veut protéger sa clé d'un visiteur pose lui-même
# CINOC_PUBLIC_MODE=true (alors seul le socle gratuit tesseract s'exécute).
ENV PYTHONUNBUFFERED=1

# ── Smoke-test du binaire au BUILD (fail-fast) ─────────────────────────────────
# Un Tesseract cassé OU figé doit casser le BUILD (le Space garde l'image
# précédente fonctionnelle), pas déployer une image qui gèle en silence :
#   (a) le binaire charge ses libs et expose la langue ``fra`` (sinon build refusé) ;
#   (b) une reconnaissance réelle sur une image générée se TERMINE sous 30 s — un
#       timeout (rc 124) = deadlock OpenMP / libs incohérentes → build refusé.
RUN set -eu; \
    timeout 30 tesseract --version; \
    timeout 30 tesseract --list-langs 2>&1 | grep -qx fra; \
    printf 'P1\n16 16\n' > /tmp/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/smoke.pbm; \
        i=$((i + 1)); \
    done; \
    rc=0; \
    timeout 30 tesseract /tmp/smoke.pbm - -l fra --psm 6 >/dev/null 2>&1 || rc=$?; \
    rm -f /tmp/smoke.pbm; \
    if [ "$rc" = 124 ]; then \
        echo "FATAL: tesseract a gelé (timeout) — build refusé, libs incohérentes." >&2; \
        exit 1; \
    fi

# ── Utilisateur non-root (sécurité d'un service exposé) ────────────────────────
RUN useradd --create-home --uid 1000 cinoc
WORKDIR /home/cinoc/app

# ── Dépendances Python d'abord (cache de couche) ───────────────────────────────
# requirements.txt fige le cœur + l'extra [serve] + pytesseract (wrapper du binaire).
COPY requirements.txt ./requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

# ── Code + paquet (sans deps : tout est déjà figé ci-dessus) ───────────────────
COPY pyproject.toml README.md ./
COPY cinoc ./cinoc
RUN pip install --no-cache-dir --no-deps .

# ── Rapports bakés (lecture seule) — dossier toujours présent ──────────────────
COPY reports ./reports
ENV CINOC_REPORTS_DIR=/home/cinoc/app/reports

USER cinoc
EXPOSE 7860
# 0.0.0.0 attendu en conteneur (le réseau est géré par la plateforme).
CMD ["cinoc", "serve", "--host", "0.0.0.0", "--port", "7860"]