# # Dockerfile.recording-rasterizer - Session Replay Recording Rasterizer Worker # # Temporal worker that records session replays to video using Puppeteer + Chromium. # Based on Dockerfile.node with added Chromium and ffmpeg for video recording. # # # Build stage: Compile nodejs and dependencies # FROM ghcr.io/posthog/rust-node-container:bookworm_rust_1.91-node_26.13.0 AS nodejs-build RUN apt-get update && \ apt-get install -y --no-install-recommends \ "wget" \ "gnupg" \ && \ mkdir -p /etc/apt/keyrings && \ wget -qO - https://packages.confluent.io/clients/deb/archive.key | gpg --dearmor -o /etc/apt/keyrings/confluent-clients.gpg && \ echo "deb [signed-by=/etc/apt/keyrings/confluent-clients.gpg] https://packages.confluent.io/clients/deb/ bookworm main" > /etc/apt/sources.list.d/confluent-clients.list && \ apt-get update && \ apt-get install -y --no-install-recommends --allow-downgrades \ "make" \ "g++" \ "gcc" \ "python3" \ "librdkafka1=2.12.0-1.cflt~deb12" \ "librdkafka++1=2.12.0-1.cflt~deb12" \ "librdkafka-dev=2.12.0-1.cflt~deb12" \ # libssl pinned to the 3.0 series (ABI-stable), not an exact version: Debian rotates # point releases out of the security archive, which breaks exact pins on uncached builds. "libssl-dev=3.0.*" \ "libssl3=3.0.*" \ "zlib1g-dev" \ && \ rm -rf /var/lib/apt/lists/* WORKDIR /code COPY turbo.json package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.json ./ COPY ./bin/turbo ./bin/turbo COPY ./patches ./patches COPY ./rust ./rust COPY ./common/esbuilder/ ./common/esbuilder/ COPY ./common/plugin_transpiler/ ./common/plugin_transpiler/ COPY ./common/hogvm/typescript/ ./common/hogvm/typescript/ COPY ./nodejs/package.json ./nodejs/tsconfig.json ./nodejs/ COPY ./common/replay-shared/package.json ./common/replay-shared/ COPY ./common/replay-headless/package.json ./common/replay-headless/ SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"] ENV BUILD_LIBRDKAFKA=0 \ PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true # The base image ships standalone Yarn 1 links that block corepack's own yarn shims. RUN rm -rf /usr/local/bin/yarn /usr/local/bin/yarnpkg /opt/yarn-v* && \ npm install -g corepack@0.36.0 RUN --mount=type=cache,id=pnpm,target=/tmp/pnpm-store-v24 \ corepack enable && \ NODE_OPTIONS="--max-old-space-size=16384" CI=1 pnpm --filter=@posthog/nodejs... install --frozen-lockfile --store-dir /tmp/pnpm-store-v24 && \ NODE_OPTIONS="--max-old-space-size=16384" CI=1 pnpm --filter=@posthog/replay-headless... install --frozen-lockfile --store-dir /tmp/pnpm-store-v24 && \ NODE_OPTIONS="--max-old-space-size=16384" CI=1 pnpm --filter=@posthog/plugin-transpiler... install --frozen-lockfile --store-dir /tmp/pnpm-store-v24 && \ NODE_OPTIONS="--max-old-space-size=16384" bin/turbo --filter=@posthog/plugin-transpiler build COPY ./nodejs/src/ ./nodejs/src/ COPY ./nodejs/tests/ ./nodejs/tests/ COPY ./nodejs/assets/ ./nodejs/assets/ COPY ./nodejs/bin/ ./nodejs/bin/ COPY ./common/replay-shared/src/ ./common/replay-shared/src/ COPY ./common/replay-shared/tsconfig.json ./common/replay-shared/ COPY ./common/replay-headless/src/ ./common/replay-headless/src/ COPY ./common/replay-headless/build.mjs ./common/replay-headless/ RUN NODE_OPTIONS="--max-old-space-size=16384" bin/turbo --filter=@posthog/replay-headless build RUN NODE_OPTIONS="--max-old-space-size=16384" bin/turbo --filter=@posthog/nodejs build # # Runtime stage: Node.js + chrome-headless-shell + ffmpeg # # Uses chrome-headless-shell instead of full Chromium. This binary supports # HeadlessExperimental.beginFrame for deterministic frame-by-frame capture # via puppeteer-capture (virtual time, no rAF dependency). # # Major-only tag so weekly rebuilds pick up the latest 26.x (node + bundled OpenSSL/V8 patches). # Must stay same major as the build stage (node 26) for native-addon ABI. Trixie, not bookworm: # the uWebSockets.js binary for Node 26 needs glibc 2.38, and bookworm ships 2.36. Addons that # the bookworm build stage links still load here, because glibc is backward compatible. FROM node:26-trixie-slim WORKDIR /code SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"] # Cache-bust marker for the floated deps below (ffmpeg, chrome-headless-shell). The weekly # scheduled rebuild passes a changing value, invalidating the apt/npx layers so security # updates are actually re-fetched; normal builds pass the constant default and reuse cache. # Without this, the unpinned layers' cache key never changes and the rebuild is a no-op. ARG DEPS_CACHE_BUST=pinned RUN echo "deps cache bust: ${DEPS_CACHE_BUST}" # Install runtime dependencies: librdkafka, chrome-headless-shell deps, ffmpeg RUN apt-get update && \ apt-get install -y --no-install-recommends \ "ca-certificates" \ "wget" \ "gnupg" \ "unzip" \ && \ mkdir -p /etc/apt/keyrings && \ wget -qO - https://packages.confluent.io/clients/deb/archive.key | gpg --dearmor -o /etc/apt/keyrings/confluent-clients.gpg && \ # The bookworm repo, because Confluent publishes no 2.12.0 build for trixie. The runtime # librdkafka must match the 2.12.0 headers that the build stage compiles node-rdkafka against. echo "deb [signed-by=/etc/apt/keyrings/confluent-clients.gpg] https://packages.confluent.io/clients/deb/ bookworm main" > /etc/apt/sources.list.d/confluent-clients.list && \ apt-get update && \ apt-get install -y --no-install-recommends --allow-downgrades \ "librdkafka1=2.12.0-1.cflt~deb12" \ "librdkafka++1=2.12.0-1.cflt~deb12" \ # libssl pinned to the 3.5 series (ABI-stable), not an exact version: Debian rotates # point releases out of the security archive, which breaks exact pins on uncached builds. "libssl3t64=3.5.*" \ # ffmpeg intentionally unpinned: tracks the latest trixie candidate so security point-updates flow in on rebuild "ffmpeg" \ # Fonts for recorded pages: without them CJK, Thai, Indic and emoji render as empty boxes, and common UI font # stacks (Arial, Helvetica, Roboto) fall back to the wider DejaVu, which changes how text wraps and truncates. fonts-liberation \ fonts-noto-cjk \ fonts-noto-color-emoji \ fonts-noto-core \ # Shared libraries required by chrome-headless-shell libasound2t64 \ libatk-bridge2.0-0t64 \ libatk1.0-0t64 \ libcups2t64 \ libdbus-1-3 \ libdrm2 \ libgbm1 \ libglib2.0-0t64 \ libnspr4 \ libnss3 \ libpango-1.0-0 \ libx11-6 \ libxcb1 \ libxcomposite1 \ libxdamage1 \ libxext6 \ libxfixes3 \ libxkbcommon0 \ libxrandr2 \ && \ rm -rf /var/lib/apt/lists/* # Install chrome-headless-shell via @puppeteer/browsers CLI. # This provides the minimal Chrome binary that supports HeadlessExperimental. # Tracks the stable channel so security patches flow in; the weekly scheduled rebuild # re-resolves it and the validation step below guards install-time breakage. RUN npx --yes @puppeteer/browsers install chrome-headless-shell@stable \ --path /opt/chrome-headless-shell && \ ln -s "$(find /opt/chrome-headless-shell -name chrome-headless-shell -type f | head -1)" /usr/local/bin/chrome-headless-shell RUN groupadd -g 10001 posthog && \ useradd -u 10001 -g posthog -m -d /home/posthog posthog && \ chown -R posthog:posthog /code USER posthog ARG COMMIT_HASH RUN echo ${COMMIT_HASH:-unknown} > /code/commit.txt # Copy compiled nodejs from build stage COPY --from=nodejs-build --chown=posthog:posthog /code/common/plugin_transpiler/dist /code/common/plugin_transpiler/dist COPY --from=nodejs-build --chown=posthog:posthog /code/common/plugin_transpiler/node_modules /code/common/plugin_transpiler/node_modules COPY --from=nodejs-build --chown=posthog:posthog /code/common/plugin_transpiler/package.json /code/common/plugin_transpiler/package.json COPY --from=nodejs-build --chown=posthog:posthog /code/common/hogvm/typescript/dist /code/common/hogvm/typescript/dist COPY --from=nodejs-build --chown=posthog:posthog /code/common/hogvm/typescript/node_modules /code/common/hogvm/typescript/node_modules COPY --from=nodejs-build --chown=posthog:posthog /code/common/hogvm/typescript/package.json /code/common/hogvm/typescript/package.json COPY --from=nodejs-build --chown=posthog:posthog /code/nodejs/dist /code/nodejs/dist COPY --from=nodejs-build --chown=posthog:posthog /code/node_modules /code/node_modules COPY --from=nodejs-build --chown=posthog:posthog /code/nodejs/node_modules /code/nodejs/node_modules COPY --from=nodejs-build --chown=posthog:posthog /code/nodejs/package.json /code/nodejs/package.json COPY --from=nodejs-build --chown=posthog:posthog /code/common/replay-headless/dist /code/common/replay-headless/dist COPY --from=nodejs-build --chown=posthog:posthog /code/common/replay-headless/package.json /code/common/replay-headless/package.json ENV NODE_ENV=production \ BUILD_LIBRDKAFKA=0 \ PUPPETEER_EXECUTABLE_PATH=/usr/local/bin/chrome-headless-shell \ PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true # Validate runtime dependencies RUN chrome-headless-shell --version && ffmpeg -version EXPOSE 6738 CMD ["node", "nodejs/dist/session-replay/recording-rasterizer/index.js"]