-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Expand file tree
/
Copy pathDockerfile.ml-mirror-image-scrub
More file actions
89 lines (77 loc) · 5.41 KB
/
Copy pathDockerfile.ml-mirror-image-scrub
File metadata and controls
89 lines (77 loc) · 5.41 KB
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
# 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.
#
# ECR retention on posthog-ml-mirror-image-scrub has to outlive the gap between builds, because prod
# pins an exact digest (PostHog/charts state/) and a master push only rebuilds this image when the
# paths filtered in .github/workflows/ci-ml-mirror-image-scrub-container.yml change. Once a pinned
# digest expires, running pods keep serving from their cached copy, so the loss only surfaces as new
# nodes failing to pull.
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 posthog && \
useradd -u 10001 -g posthog -m -d /home/posthog posthog && \
chown -R posthog:posthog /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 posthog -c "node_modules/.bin/tsx src/smoke.ts"
USER posthog
ENV NODE_ENV=production
CMD ["node_modules/.bin/tsx", "src/main.ts"]