# ── Stage 1: install workspace deps (runs natively on build host) ────────────── FROM oven/bun:1.3.14 AS deps WORKDIR /app # nodejs/python3/make/g++ are needed to compile better-sqlite3 (camoufox-js dep) RUN apt-get update && apt-get install -y --no-install-recommends \ nodejs npm python3 make g++ \ && rm -rf /var/lib/apt/lists/* COPY package.json bun.lock* ./ COPY packages/types/package.json ./packages/types/ COPY packages/browser/package.json ./packages/browser/ COPY packages/tiers/package.json ./packages/tiers/ COPY apps/api/package.json ./apps/api/ COPY apps/web/package.json ./apps/web/ COPY apps/docs/package.json ./apps/docs/ # --production skips devDependencies (vitepress + algolia from docs, typescript, @types) # --linker=hoisted avoids Bun's default isolated-linker bugs that corrupt # multi-stage Docker builds: transitive deps not symlinked (oven-sh/bun#23524, # e.g. @sinclair/typebox via elysia) and a store-population race producing # EISDIR on freshly-linked packages (oven-sh/bun#29489, e.g. camoufox-js). RUN bun install --frozen-lockfile --production --linker=hoisted # ── Stage 2: fetch the Camoufox Firefox binary ───────── FROM oven/bun:1.3.14 AS camoufox ENV CAMOUFOX_INSTALL_DIR=/opt/camoufox RUN apt-get update && apt-get install -y --no-install-recommends \ nodejs npm python3 make g++ && rm -rf /var/lib/apt/lists/* # Use BuildKit cache for the download RUN --mount=type=secret,id=GITHUB_TOKEN,env=GITHUB_TOKEN \ --mount=type=cache,target=/root/.cache/camoufox,sharing=locked \ bun x camoufox-js fetch \ && rm -rf /opt/camoufox/fonts/macos /opt/camoufox/fonts/windows # Secret mount keeps GITHUB_TOKEN out of image layers. # Token prevents GitHub API rate-limits that cause the binary download to stall. # ── Stage 3: lean runtime (only API-required files) ──────────────────────────── FROM ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive ARG TARGETARCH ARG BUN_VERSION=1.3.14 RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ apt-get update && apt-get install -y --no-install-recommends \ libatk1.0-0 libatk-bridge2.0-0 libatspi2.0-0 \ libcairo2 libcairo-gobject2 \ libdbus-1-3 libdbus-glib-1-2 \ libfontconfig1 \ libgdk-pixbuf-2.0-0 \ libglib2.0-0 \ libgtk-3-0 \ libnspr4 libnss3 \ libpango-1.0-0 libpangocairo-1.0-0 \ libx11-6 libx11-xcb1 libxcb1 libxcb-shm0 \ libxcomposite1 libxcursor1 libxdamage1 \ libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \ libdrm2 libgbm1 \ libasound2 \ fonts-liberation \ ca-certificates unzip curl \ && apt-get clean # Pull the Bun runtime directly from GitHub releases. # amd64 uses the BASELINE variant (pre-2013 CPUs without AVX2 — covers older # Synology/Atom/Kabylake-era hardware). arm64 has no baseline variant because # arm64 doesn't use x86 SIMD — the standard `bun-linux-aarch64.zip` works on # all armv8 CPUs (including every ARM64 Synology). RUN set -eux; \ case "$TARGETARCH" in \ amd64) BUN_ZIP=bun-linux-x64-baseline.zip ;; \ arm64) BUN_ZIP=bun-linux-aarch64.zip ;; \ *) echo "unsupported arch: $TARGETARCH"; exit 1 ;; \ esac; \ curl -fsSL "https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}/${BUN_ZIP}" -o /tmp/bun.zip; \ unzip -q /tmp/bun.zip -d /tmp/bun-extract; \ BUN_BIN=$(find /tmp/bun-extract -name bun -type f | head -1); \ install -m 0755 "$BUN_BIN" /usr/local/bin/bun; \ rm -rf /tmp/bun.zip /tmp/bun-extract; \ /usr/local/bin/bun --version COPY --from=camoufox /opt/camoufox /opt/camoufox COPY --from=deps /app/node_modules /app/node_modules COPY packages/types/ /app/packages/types/ COPY packages/browser/ /app/packages/browser/ COPY packages/tiers/ /app/packages/tiers/ COPY apps/api/ /app/apps/api/ COPY package.json /app/ ENV CAMOUFOX_INSTALL_DIR=/opt/camoufox WORKDIR /app EXPOSE 8191 CMD ["bun", "run", "apps/api/src/index.ts"]