Files
insights/Dockerfile.ml-mirror-image-scrub
zeekay 1b367e2b62 chore: rebase the tree onto upstream master
The tree was a debranded snapshot of upstream taken around 2026-02-20, and
upstream has moved about five months since. This replaces it with upstream
master, mechanically re-derived: bin/debrand does the rename, and the
pipeline and squashed migrations are restored from the shipped tree.

Not a merge. The previous import replaced the tree wholesale in a single
commit, so there is no shared content history to merge against. Reproducing
the rename on the new tree is what actually converges.

What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints,
compose files, LLM.md, and insights/migrations. The squashed initial
migration is load-bearing — the live database records it by name, so
upstream's ~918 individual migrations cannot come back.

Nine products are dropped as unreferenced. Twenty-one more that we do not run
are kept anyway because the core app still imports them; unpicking those is a
refactor and does not belong in a rebase.

This base does not build yet, and the reasons are not in the rename:
upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against
our 4.2, and the dependency manifests still name upstream packages that we
republish under our own names. Those are decisions, not renames.
2026-08-01 15:44:40 -07:00

84 lines
5.0 KiB
Docker

# syntax=docker/dockerfile:1.7
#
# Dockerfile.ml-mirror-image-scrub - Session Replay ML-mirror image-scrub sidecar
#
# An HTTP service that turns raw image bytes into scrubbed bytes (NSFW gate + solid-fill of detected
# faces, text, and QR/barcodes). The plugin-server consumer owns Kafka + S3 and calls this over
# loopback; it runs as a sidecar container. The ML runtime ships in the image: the ONNX Runtime
# (onnxruntime-node) native binary lands via its install script, zxing is wasm, and the three ONNX
# models are baked in below — the sidecar makes no network fetches at startup.
#
# Deliberately NOT the plugin-server workspace build: standalone so the heavy ML deps stay out of the
# main image. sharp ships prebuilt libvips.
#
# Exact node version pinned to .nvmrc (24.13.0), matching the main Dockerfile's node stages, so the
# build is reproducible instead of drifting with the floating major tag; renovate bumps it. -bookworm-slim
# so glibc matches sharp's prebuilt binaries.
FROM node:24.13.0-bookworm-slim
WORKDIR /code/app
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
ARG COMMIT_HASH
RUN echo ${COMMIT_HASH:-unknown} > /code/commit.txt
# Production deps only (sharp/libvips and the ML natives ship prebuilt; prom-client/tsx are pure JS).
# Only this package is copied (no root pnpm-workspace.yaml), so pnpm treats this dir as its own
# project root.
COPY nodejs/src/ingestion/pipelines/sessionreplay/ml-mirror-image-scrub-sidecar/package.json nodejs/src/ingestion/pipelines/sessionreplay/ml-mirror-image-scrub-sidecar/pnpm-lock.yaml ./
RUN --mount=type=cache,id=pnpm,target=/tmp/pnpm-store-v24 \
corepack enable && \
CI=1 pnpm install --prod --frozen-lockfile --store-dir /tmp/pnpm-store-v24
COPY nodejs/src/ingestion/pipelines/sessionreplay/ml-mirror-image-scrub-sidecar/tsconfig.json ./
COPY nodejs/src/ingestion/pipelines/sessionreplay/ml-mirror-image-scrub-sidecar/src ./src
# Tests co-locate in src/ but must not ship in the runtime image.
RUN rm -f src/*.test.ts
# The ONNX models (safety gate, face, text), pinned to immutable upstream refs and checksum-verified
# by BuildKit; loadModels() reads them from WORKDIR-relative models/. Keep URLs + digests in sync
# with dev/setup.ts (the dev-machine download path).
ADD --checksum=sha256:f139598bc2af4e4b6fe98dec11574e30edfdd91fc94ac1425c18ace3bd5a866b \
https://huggingface.co/SWHL/RapidOCR/resolve/1cfba2e90fc938db55889873735088de210cc173/PP-OCRv4/en_PP-OCRv3_det_infer.onnx \
models/dbnet_det.onnx
ADD --checksum=sha256:8f2383e4dd3cfbb4553ea8718107fc0423210dc964f9f4280604804ed2552fa4 \
https://github.com/opencv/opencv_zoo/raw/47534e27c9851bb1128ccc0102f1145e27f23f98/models/face_detection_yunet/face_detection_yunet_2023mar.onnx \
models/yunet.onnx
ADD --checksum=sha256:8c28c49d9075f3ad15ebdc2961f02d5b3f99be944815b848b49c9f0e6f3fb689 \
https://huggingface.co/OwenElliott/image-safety-classifier-xs/resolve/54f4560bd9c5ee92d45dc30418a8f8680e80de6d/onnx/image-safety-classifier-xs.onnx \
models/safety.onnx
RUN groupadd -g 10001 insights && \
useradd -u 10001 -g insights -m -d /home/insights insights && \
chown -R insights:insights /code
# Native pools are bounded so they cannot oversubscribe a CPU-limited pod. sharp is pinned to one
# thread per operation at module load in src/blur.ts (sharp.concurrency(1) + cache(false), imported
# by every scrub path), and OpenMP likewise. ORT is sized in src/cores.ts as cores/SCRUB_WORKERS,
# since onnxruntime-node's run blocks its thread: each worker runs one inference at a time, so it is
# threads times workers that has to fit the quota. With one worker per core that lands on 1 each.
ENV OMP_NUM_THREADS=1
# libuv reads this when it first creates its thread pool and never resizes, which happens before any
# entry-module code under a loader like tsx, so it cannot be set from JS. The pool is process-wide
# and shared by every worker thread, and each worker's sharp stages queue onto it, so size it by
# SCRUB_WORKERS (one core per worker) rather than by request concurrency. Below the worker count the
# sharp stages re-serialise on a pod with more cores than this.
ENV UV_THREADPOOL_SIZE=8
# Each worker loads its own three ONNX sessions, zxing wasm module and V8 isolate, because sessions
# cannot be shared across isolates. Memory therefore scales with SCRUB_WORKERS, which defaults to the
# core count: size the pod's memory limit against cores, not against a single process, or a
# many-core node OOM-kills the sidecar into a crash loop. Set SCRUB_WORKERS to cap it explicitly.
# Prove at build time that the models parse, the native/wasm runtimes load, and a scrub runs end to
# end — a broken model download or prebuilt-binary mismatch fails the image build, not the deploy.
# --network=none doubles as a guarantee that startup has no network dependency, which is also why
# tsx is invoked directly: `pnpm` goes through corepack, which wants to fetch pnpm per-user.
RUN --network=none su insights -c "node_modules/.bin/tsx src/smoke.ts"
USER insights
ENV NODE_ENV=production
CMD ["node_modules/.bin/tsx", "src/main.ts"]