11# ========================================
22# Combined Presidio service (analyzer + anonymizer) on a single port (5001)
33#
4- # Targets (docker builds the LAST stage when no --target is given):
5- # (default) / spacy : lean image, spaCy NER only — what self-hosters get.
6- # gliner : superset image (torch CPU + gliner + baked GLiNER
7- # model + small spaCy models). Both PII_ENGINE=spacy
8- # and PII_ENGINE=gliner work in it.
9- # gliner-gpu : scaffold for the EC2-GPU fleet — same layout, CUDA
10- # torch wheels (bundle their own CUDA libs; host only
11- # needs the nvidia container runtime). Not built in CI.
4+ # ONE image serves both NER engines — the engine is a pure runtime choice via
5+ # PII_ENGINE (spacy default | gliner). spaCy large models, torch (CPU), the
6+ # gliner package, and the baked GLiNER weights all ship in it, so flipping
7+ # engines never requires an image swap.
128#
13- # Source files are COPY'd last in each terminal stage so code edits never
14- # re-download deps or models.
9+ # GPU variant (EC2-GPU fleet follow-up): same Dockerfile, CUDA torch wheels —
10+ # docker build --build-arg TORCH_INDEX_URL=https://download.pytorch.org/whl/cu128 ...
11+ # (torch CUDA wheels bundle their own CUDA libs; the host only needs the
12+ # nvidia container runtime.)
13+ #
14+ # Source files are COPY'd last so code edits never re-download deps or models.
1515# ========================================
1616FROM python:3.12-slim-bookworm AS base
1717
@@ -29,35 +29,9 @@ COPY apps/pii/requirements.txt ./requirements.txt
2929RUN --mount=type=cache,target=/root/.cache/pip \
3030 pip install -r requirements.txt
3131
32- RUN groupadd -g 1001 pii && \
33- useradd -u 1001 -g pii pii && \
34- chown -R pii:pii /app
35-
36- # Listen on 5001. Runs as its own ECS service (separate task), reached via PII_URL;
37- # 5001 avoids colliding with the app's 3000 in local/compose runs on one host.
38- EXPOSE 5001
39-
40- # start-period covers the model cold start. With PII_WORKERS>1 each worker loads
41- # the five spaCy models independently and in parallel, so allow generous headroom
42- # (memory-bandwidth contention stretches the wall-time beyond the single-worker case).
43- HEALTHCHECK --interval=30s --timeout=5s --start-period=300s --retries=3 \
44- CMD curl -fsS http://localhost:5001/health || exit 1
45-
46- # Worker count is env-driven so ONE image scales per task size: set PII_WORKERS to
47- # the task's vCPU count (each worker loads the models independently, ~3 GB each, so
48- # size task memory ≈ PII_WORKERS × 3 GB + overhead). Defaults to 1 for local/small.
49- # `sh -c exec` expands the env var while keeping uvicorn as PID 1 for clean SIGTERM.
50- # Quote the expansion so a malformed PII_WORKERS fails uvicorn arg-parsing rather
51- # than being interpreted by the shell.
52- # NB for the gliner engine: EACH worker loads its own GLiNER model copy (into GPU
53- # memory when on cuda), so GPU deployments generally want PII_WORKERS=1 per GPU.
54- CMD ["sh" , "-c" , "exec uvicorn server:app --host 0.0.0.0 --port 5001 --workers \" ${PII_WORKERS:-1}\" " ]
55-
5632# Pinned spaCy models (en + es/it/pl/fi, ~2.2GB total). Downloaded with
5733# retries/resume — the large wheels truncate on flaky networks if pip fetches
58- # the URLs directly. Shared by every terminal image so PII_ENGINE=spacy works
59- # everywhere (the gliner opt-in stays reversible without an image swap).
60- FROM base AS spacy-models
34+ # the URLs directly.
6135ARG SPACY_MODELS="en_core_web_lg-3.8.0 es_core_news_lg-3.8.0 it_core_news_lg-3.8.0 pl_core_news_lg-3.8.0 fi_core_news_lg-3.8.0"
6236RUN --mount=type=cache,target=/root/.cache/pip \
6337 for model in ${SPACY_MODELS}; do \
@@ -69,15 +43,14 @@ RUN --mount=type=cache,target=/root/.cache/pip \
6943 pip install /tmp/*.whl && \
7044 rm /tmp/*.whl
7145
72- # --- GLiNER (CPU) ------------------------------------------------------------
73- FROM spacy-models AS gliner
74-
75- # torch pinned here (not requirements-gliner.txt) because the CPU and CUDA
76- # targets install the same version from different wheel indexes. 2.11.0 is the
46+ # --- GLiNER engine deps -------------------------------------------------------
47+ # torch is pinned here (not requirements-gliner.txt) because the CPU and CUDA
48+ # builds install the same version from different wheel indexes. 2.11.0 is the
7749# newest release published on both the cpu and cu128 indexes for py312.
7850ARG TORCH_VERSION=2.11.0
51+ ARG TORCH_INDEX_URL=https://download.pytorch.org/whl/cpu
7952RUN --mount=type=cache,target=/root/.cache/pip \
80- pip install torch==${TORCH_VERSION} --index-url https://download.pytorch.org/whl/cpu
53+ pip install torch==${TORCH_VERSION} --index-url ${TORCH_INDEX_URL}
8154
8255COPY apps/pii/requirements-gliner.txt ./requirements-gliner.txt
8356RUN --mount=type=cache,target=/root/.cache/pip \
@@ -105,54 +78,38 @@ RUN python -c "from gliner import GLiNER; GLiNER.from_pretrained('${GLINER_MODEL
10578 chmod -R a+rX /opt/hf-cache
10679ENV HF_HUB_OFFLINE=1
10780
108- # Bench + tests ride along only in this image — it's the only one with both
109- # engines installed (see apps/pii/scripts/bench_engines.py). pytest/httpx are
110- # baked in so the documented `docker run ... python -m pytest tests` works
111- # as-is (the runtime user has no writable HOME for pip install --user).
81+ # pytest/httpx for the in-image test suites (tests/) — baked in because the
82+ # runtime user has no writable HOME for pip install --user.
11283COPY apps/pii/requirements-dev.txt ./requirements-dev.txt
11384RUN --mount=type=cache,target=/root/.cache/pip \
11485 pip install -r requirements-dev.txt
11586
87+ RUN groupadd -g 1001 pii && \
88+ useradd -u 1001 -g pii pii && \
89+ chown -R pii:pii /app
90+
11691COPY --chown=pii:pii apps/pii/server.py apps/pii/engines.py ./
11792COPY --chown=pii:pii apps/pii/scripts ./scripts
11893COPY --chown=pii:pii apps/pii/tests ./tests
11994
12095USER pii
12196
122- # --- GLiNER (CUDA) scaffold — wired for the GPU fleet follow-up --------------
123- FROM spacy-models AS gliner-gpu
124-
125- ARG TORCH_VERSION=2.11.0
126- RUN --mount=type=cache,target=/root/.cache/pip \
127- pip install torch==${TORCH_VERSION} --index-url https://download.pytorch.org/whl/cu128
128-
129- COPY apps/pii/requirements-gliner.txt ./requirements-gliner.txt
130- RUN --mount=type=cache,target=/root/.cache/pip \
131- pip install -r requirements-gliner.txt
132-
133- ARG SPACY_SM_MODELS="en_core_web_sm-3.8.0 es_core_news_sm-3.8.0 it_core_news_sm-3.8.0 pl_core_news_sm-3.8.0 fi_core_news_sm-3.8.0"
134- RUN --mount=type=cache,target=/root/.cache/pip \
135- for model in ${SPACY_SM_MODELS}; do \
136- whl="${model}-py3-none-any.whl" ; \
137- curl -fL --retry 5 --retry-delay 5 --retry-all-errors -C - \
138- -o "/tmp/${whl}" \
139- "https://github.com/explosion/spacy-models/releases/download/${model}/${whl}" || exit 1; \
140- done && \
141- pip install /tmp/*.whl && \
142- rm /tmp/*.whl
143-
144- ENV HF_HOME=/opt/hf-cache
145- ARG GLINER_MODEL=urchade/gliner_multi_pii-v1
146- RUN python -c "from gliner import GLiNER; GLiNER.from_pretrained('${GLINER_MODEL}')" && \
147- chmod -R a+rX /opt/hf-cache
148- ENV HF_HUB_OFFLINE=1
149-
150- COPY --chown=pii:pii apps/pii/server.py apps/pii/engines.py ./
151- COPY --chown=pii:pii apps/pii/scripts ./scripts
97+ # Listen on 5001. Runs as its own ECS service (separate task), reached via PII_URL;
98+ # 5001 avoids colliding with the app's 3000 in local/compose runs on one host.
99+ EXPOSE 5001
152100
153- USER pii
101+ # start-period covers the model cold start. With PII_WORKERS>1 each worker loads
102+ # the five spaCy models independently and in parallel, so allow generous headroom
103+ # (memory-bandwidth contention stretches the wall-time beyond the single-worker case).
104+ HEALTHCHECK --interval=30s --timeout=5s --start-period=300s --retries=3 \
105+ CMD curl -fsS http://localhost:5001/health || exit 1
154106
155- # --- DEFAULT (last stage): the lean spaCy image, content-equivalent to today -
156- FROM spacy-models AS spacy
157- COPY --chown=pii:pii apps/pii/server.py apps/pii/engines.py ./
158- USER pii
107+ # Worker count is env-driven so ONE image scales per task size: set PII_WORKERS to
108+ # the task's vCPU count (each worker loads the models independently, ~3 GB each, so
109+ # size task memory ≈ PII_WORKERS × 3 GB + overhead). Defaults to 1 for local/small.
110+ # `sh -c exec` expands the env var while keeping uvicorn as PID 1 for clean SIGTERM.
111+ # Quote the expansion so a malformed PII_WORKERS fails uvicorn arg-parsing rather
112+ # than being interpreted by the shell.
113+ # NB for the gliner engine: EACH worker loads its own GLiNER model copy (into GPU
114+ # memory when on cuda), so GPU deployments generally want PII_WORKERS=1 per GPU.
115+ CMD ["sh" , "-c" , "exec uvicorn server:app --host 0.0.0.0 --port 5001 --workers \" ${PII_WORKERS:-1}\" " ]
0 commit comments