Files
sim/docker/app.Dockerfile
T
Waleed 856fe0ffb6 fix(docker): upgrade bun to 1.3.14 (#6236)
* fix(docker): upgrade bun to 1.3.14 to unbreak the Next 16.3.0 server

Bun 1.3.13 cannot load Next 16.3.0's compiled server runtime. The app container
runs the Next server under Bun (`oven/bun:1.3.13-slim`, `bun apps/sim/bootstrap.js`),
so every app-page render threw and `/api/health` returned 500:

  ⨯ Error: Failed to load external module
    next/dist/compiled/next-server/app-page-turbo.runtime.prod.js:
    TypeError: Expected CommonJS module to have a function wrapper.
    If you weren't messing around with Bun's internals, this is a bug in Bun

Isolated to Bun, not Next, by loading that exact module in the real images:

  Next 16.2.12 + Bun 1.3.13 -> loads (why staging was fine before)
  Next 16.3.0  + Bun 1.3.13 -> CJS wrapper error
  Next 16.3.0  + Bun 1.3.14 -> loads

Bun 1.3.14 is the current stable and already fixes it, so this bumps every pin
rather than reverting the framework upgrade, which would only defer the same
latent Bun bug to the next attempt.

Why no gate caught it: local dev machines and this bump's own verification run
Bun 1.3.14, while the container and CI pinned 1.3.13 — and CI only *builds* the
image, it never boots one and probes `/api/health`. A container smoke test in CI
would have caught this before merge; that is worth adding separately.

* fix(docker): align the remaining bun pins with 1.3.14

Two pins were missed in the first pass because the search was scoped to
docker/, package.json and .github/workflows/:

- .devcontainer/Dockerfile still built on oven/bun:1.3.13-alpine
- PI_BUN_VERSION in apps/sim/scripts/pi-sandbox-packages.ts was still 1.3.13,
  despite being documented as mirroring the root packageManager field, so Pi
  sandbox images would have kept installing the Bun release that cannot load
  the Next 16.3.0 server runtime.

Fixed surgically rather than with a repo-wide replace: "1.3.13" also appears
inside SVG path data in apps/sim/components/icons.tsx and
apps/docs/components/icons.tsx, which a blind sed would have corrupted.
2026-08-03 19:23:52 -07:00

157 lines
6.9 KiB
Docker

# ========================================
# Base Stage: Debian-based Bun with Node.js 24
# ========================================
FROM oven/bun:1.3.14-slim AS base
# Install Node.js 24 (Active LTS) and common dependencies once in base stage.
# Node runs only the isolated-vm sandbox worker (the app itself runs under Bun);
# the version is kept in lockstep with the `isolated-vm` pin in
# apps/sim/package.json — Node 24 (ABI 137) requires isolated-vm 6.x.
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y --no-install-recommends \
python3 python3-pip python3-venv make g++ curl ca-certificates bash ffmpeg \
&& curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
&& apt-get install -y nodejs
# ========================================
# Pruner Stage: Emit a minimal monorepo subset that sim depends on
# ========================================
FROM base AS pruner
WORKDIR /app
RUN bun install -g turbo@2.9.6
COPY . .
RUN turbo prune sim --docker
# ========================================
# Dependencies Stage: Install Dependencies
# ========================================
FROM base AS deps
WORKDIR /app
# Pruned manifests from the pruner stage. This layer only invalidates when
# package.json/bun.lock content changes — not on source edits.
COPY --from=pruner /app/out/json/ ./
# Use the full bun.lock (not the pruned out/bun.lock). turbo prune emits a
# bun.lock that bun 1.3.x rejects with "Failed to resolve prod dependency",
# forcing a slow fresh resolve. The full lockfile parses cleanly and bun
# only installs what the pruned package.jsons reference.
COPY --from=pruner /app/bun.lock ./bun.lock
# Install all dependencies (including devDependencies — tailwindcss/postcss are
# devDeps but required at build time). Then rebuild isolated-vm against Node.js.
# JOBS=4 caps node-gyp parallelism — higher values OOM isolated-vm (laverdet/isolated-vm#428).
RUN --mount=type=cache,id=bun-cache,target=/root/.bun/install/cache \
--mount=type=cache,id=npm-cache,target=/root/.npm \
HUSKY=0 bun install --ignore-scripts --linker=hoisted && \
cd node_modules/isolated-vm && JOBS=4 npx node-gyp rebuild --release
# ========================================
# Builder Stage: Build the Application
# ========================================
FROM base AS builder
ARG TARGETPLATFORM
WORKDIR /app
# Copy node_modules from deps stage (cached if dependencies don't change)
COPY --from=deps /app/node_modules ./node_modules
# Copy pruned source tree (apps/sim + workspace packages it depends on)
COPY --from=pruner /app/out/full/ ./
# Next.js 16 / Turbopack workspace-root detection looks for a lockfile next to
# the workspace package.json. Without it, `next build` fails with
# "couldn't find next/package.json from /app/apps/sim". turbo also warns
# "Lockfile not found at /app/bun.lock" without it.
COPY --from=pruner /app/bun.lock ./bun.lock
ENV NEXT_TELEMETRY_DISABLED=1 \
VERCEL_TELEMETRY_DISABLED=1 \
DOCKER_BUILD=1
# Dummy values so next build can evaluate modules. Override at runtime.
ARG DATABASE_URL="postgresql://user:pass@localhost:5432/dummy"
ENV DATABASE_URL=${DATABASE_URL}
ARG NEXT_PUBLIC_APP_URL="http://localhost:3000"
ENV NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL}
# Per-platform cache id keeps arm64/amd64 SWC artifacts isolated.
RUN --mount=type=cache,id=next-cache-${TARGETPLATFORM},target=/app/apps/sim/.next/cache \
--mount=type=cache,id=turbo-cache-${TARGETPLATFORM},target=/app/.turbo \
bun run build
# Bundle the secrets-loading bootstrap into a self-contained entrypoint. It runs
# before (and outside) the Next standalone server, so its dependencies
# (@sim/runtime-secrets, AWS SDK) are inlined here rather than resolved from the
# pruned standalone node_modules. The dynamic import of ./server.js stays a
# runtime import.
RUN bun build apps/sim/bootstrap.ts --target=bun --outfile=apps/sim/bootstrap.js
# ========================================
# Runner Stage: Run the actual app
# ========================================
FROM base AS runner
WORKDIR /app
# Node.js 24, Python, ffmpeg, etc. are already installed in base stage
ENV NODE_ENV=production
# Create non-root user and group
RUN groupadd -g 1001 nodejs && \
useradd -u 1001 -g nodejs nextjs
# Copy application artifacts from builder
COPY --from=builder --chown=nextjs:nodejs /app/apps/sim/public ./apps/sim/public
COPY --from=builder --chown=nextjs:nodejs /app/apps/sim/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/sim/.next/static ./apps/sim/.next/static
# Self-contained secrets-loading bootstrap (bundled in the builder stage). Runs
# before the standalone server.js to hydrate process.env from the runtime secret.
COPY --from=builder --chown=nextjs:nodejs /app/apps/sim/bootstrap.js ./apps/sim/bootstrap.js
# Copy blog/author content for runtime filesystem reads (not part of the JS bundle)
COPY --from=builder --chown=nextjs:nodejs /app/apps/sim/content ./apps/sim/content
# Copy isolated-vm native module (compiled for Node.js in deps stage)
COPY --from=deps --chown=nextjs:nodejs /app/node_modules/isolated-vm ./node_modules/isolated-vm
# The collab-doc seed/merge/persist routes run the converter (markdown <-> Yjs) server-side. `yjs` is a
# serverExternalPackage, and the Next standalone tracer copies it only partially — it misses ESM subpath
# files that `yjs/dist/yjs.mjs` imports through `lib0`'s exports map (e.g. `lib0/logging`), so the seed
# 500s ("Cannot find module 'lib0/logging'") and every collaborative doc is stuck read-only. Overwrite
# the partial trace with the complete packages from the full install (outputFileTracingIncludes can't:
# its globs resolve against apps/sim, but these deps hoist to the monorepo-root node_modules).
COPY --from=deps --chown=nextjs:nodejs /app/node_modules/lib0 ./node_modules/lib0
COPY --from=deps --chown=nextjs:nodejs /app/node_modules/yjs ./node_modules/yjs
COPY --from=deps --chown=nextjs:nodejs /app/node_modules/y-protocols ./node_modules/y-protocols
# Copy the isolated-vm worker script
COPY --from=builder --chown=nextjs:nodejs /app/apps/sim/lib/execution/isolated-vm-worker.cjs ./apps/sim/lib/execution/isolated-vm-worker.cjs
# Copy the pre-built sandbox library bundles (pptxgenjs, docx, pdf-lib) that
# run inside the V8 isolate. Committed into the repo; see
# apps/sim/lib/execution/sandbox/bundles/build.ts to regenerate.
COPY --from=builder --chown=nextjs:nodejs /app/apps/sim/lib/execution/sandbox/bundles ./apps/sim/lib/execution/sandbox/bundles
# Guardrails PII runs in a standalone Presidio service (combined analyzer +
# anonymizer, docker/pii.Dockerfile), reached over the network via PII_URL —
# no Python/Presidio in this image.
# Create .next/cache directory with correct ownership
RUN mkdir -p apps/sim/.next/cache && \
chown -R nextjs:nodejs apps/sim/.next/cache
# Switch to non-root user
USER nextjs
EXPOSE 3000
ENV PORT=3000 \
HOSTNAME="0.0.0.0"
CMD ["bun", "apps/sim/bootstrap.js"]