FROM node:22-bookworm-slim@sha256:83f487e0a63425e5b4d146fb5e5be574bcbe1b7b843d3ebafdd95eaf7767a7e5 AS builder RUN corepack enable && corepack prepare pnpm@10.12.1 --activate WORKDIR /app COPY . . # Fixed store dir so the runtime stage can reuse the cache offline. RUN pnpm install --frozen-lockfile --store-dir /pnpm-store # beta-review: NO default. NEXT_PUBLIC_* is inlined into the bundles at # build time, and the release workflow publishes ONE image for every # instance — a localhost default broke voice + realtime after # `lfctl update apply`. Left empty, the app resolves the endpoints at # request time (token response / same-origin /livekit + /ws proxy, see # apps/web/lib/public-endpoints.ts). Local builds may still pass them. ARG NEXT_PUBLIC_LIVEKIT_URL= ARG NEXT_PUBLIC_WS_URL= ENV NEXT_PUBLIC_LIVEKIT_URL=$NEXT_PUBLIC_LIVEKIT_URL ENV NEXT_PUBLIC_WS_URL=$NEXT_PUBLIC_WS_URL ENV NODE_ENV=production # Build only the server-side packages (web, ws-gateway, db, core, etc). # Skip desktop (Tauri needs Rust) and plugins that are consumed as source. RUN pnpm --filter @lobbyforge/config build && \ pnpm --filter @lobbyforge/core build && \ pnpm --filter @lobbyforge/plugin-sdk build && \ pnpm --filter @lobbyforge/bot-sdk build && \ pnpm --filter @lobbyforge/db build && \ pnpm --filter @lobbyforge/ui build && \ pnpm --filter @lobbyforge/registry build && \ pnpm --filter @lobbyforge/ws-gateway build && \ pnpm --filter @lobbyforge/plugin-worker build && pnpm --filter @lobbyforge/web build FROM node:22-bookworm-slim@sha256:83f487e0a63425e5b4d146fb5e5be574bcbe1b7b843d3ebafdd95eaf7767a7e5 AS runtime # PostgreSQL client tools for lfctl backup create/restore (pg_dump, pg_restore, psql). # libpcre2-8-0 is listed so apt upgrades it to the patched Debian build: the # base image lags behind Debian's pcre2 security fixes (CVE-2026-103111 and # earlier, fixed in 10.42-1+deb12u2). RUN apt-get update && apt-get install -y --no-install-recommends postgresql-client libpcre2-8-0 && rm -rf /var/lib/apt/lists/* RUN corepack enable && corepack prepare pnpm@10.12.1 --activate WORKDIR /app ENV NODE_ENV=production COPY --from=builder /pnpm-store /pnpm-store COPY --from=builder /app /app # Strip devDependencies from the RUNTIME image (Trivy CRITICAL gate: # vitest/happy-dom/tar CVEs were shipping because the builder's full # node_modules was copied). `pnpm prune` proved pathologically slow in # CI (>1h); instead rebuild node_modules PROD-ONLY, offline, from the # store the builder already populated — same lockfile, no network. # Workspace symlinks (@lobbyforge/db etc.) are recreated against the # packages' built dist/, which the COPY above preserved. # Remove EVERY node_modules (root AND per-package — the builder copy # leaves nested ones with dev-only transitive deps like tar@7.4.3). # Wipe EVERY node_modules (root AND per-package — the builder copy # leaves nested ones with dev-only transitive deps), rebuild PROD-ONLY # offline from the builder's store, then drop the store (it also holds # DEV tarballs) AND the package managers themselves — pnpm's corepack # download bundles a vulnerable tar (CVE-2026-59873) and npm's manifests # ship more. The runtime needs NODE only; the web service invokes # next's bin directly. RUN find /app -name node_modules -type d -prune -exec rm -rf {} + && pnpm install --prod --frozen-lockfile --offline --ignore-scripts --store-dir /pnpm-store && rm -rf /pnpm-store /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx /root/.cache/node/corepack # Run as non-root — the node image ships with a `node` user (uid 1000). RUN chown -R node:node /app USER node EXPOSE 3000 3001 # libuv's threadpool (default 4 threads) runs scrypt password hashing, # zlib, file I/O and dns.lookup (getaddrinfo) for the whole process; a few # slow lookups (the plugin installer, the directory, CAPTCHA siteverify) # must not stall sign-ins. Bot event deliveries resolve off the pool # (c-ares, 2 s deadline), but the web server still gets a bigger pool. # Read once at process start; override in .env.prod (env_file) if needed. ENV UV_THREADPOOL_SIZE=16 # Start only the Next.js web server (direct node invocation — no pnpm # in the runtime image). The ws-gateway runs as a separate Compose # service with its own CMD override. WORKDIR /app/apps/web CMD ["node", "node_modules/next/dist/bin/next", "start"]