You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'm not sure if this is possible at all, but I'm running into issues when compiling .next/standalone/server.js into a binary for use in a container image.
When I try using bun build --compile, it fails to resolve any of the CommonJS require() dependencies, even when I call it from within the .next/standalone directory. This causes the entire compilation process to fail, and I don't get a binary produced.
I also tried the @yao-pkg/pkg module, which does actually produce a binary, but when you try to run it, the binary fails because it's trying to chdir to a weird directory (/snapshot/$app/.next/standalone) which doesn't exist.
Why?
The Next.js recommended Dockerfile is both useful and very efficient when it comes to building container images, but I was wondering whether the final image size could be reduced a bit by compiling the Next.js application server into a static binary first, and then copying that over to the final image. This would let you source the runtime stage FROM alpine rather than the node:18-alpine base image, which I hypothesized might make the final image size a lot smaller, since it doesn't need to include anything else related to Node.js development (like npm or corepack) which might be included in a general Node.js base image.
Other deployment methods may also benefit from this approach. For example, Tauri currently doesn't support the Next.js server in production, but it does support bundling a Node.js sidecar application to run, which theoretically could include a web server. This means it may be possible to run a "fully-featured" Next.js app as the frontend to a Tauri native application, if the server could be compiled to a binary...
Additional information
Not sure if anyone else has tried this before, or whether anyone went down this path and found out it really doesn't matter either way...I can see that because the whole bun/node runtime needs to be included in the executable, so maybe it's just a negligible improvement?
Example
Here's what the (slightly edited for brevity) Dockerfile might look like if this was possible:
FROM node:18-alpine AS base
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN yarn build
RUN npx @yao-pkg/pkg@latest -- .next/standalone/server.js -t node18-linux
FROM alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"CMD ["./server-linux"]
And here's what it might look like using Bun:
FROM oven/bun:1-alpine AS base
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN bun run build
RUN bun build --compile --outfile=server .next/standalone/server.js
FROM alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder /app/server /usr/bin/server
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"CMD ["server"]
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Summary
I'm not sure if this is possible at all, but I'm running into issues when compiling
.next/standalone/server.js
into a binary for use in a container image.When I try using
bun build --compile
, it fails to resolve any of the CommonJSrequire()
dependencies, even when I call it from within the.next/standalone
directory. This causes the entire compilation process to fail, and I don't get a binary produced.I also tried the
@yao-pkg/pkg
module, which does actually produce a binary, but when you try to run it, the binary fails because it's trying tochdir
to a weird directory (/snapshot/$app/.next/standalone
) which doesn't exist.Why?
The Next.js recommended Dockerfile is both useful and very efficient when it comes to building container images, but I was wondering whether the final image size could be reduced a bit by compiling the Next.js application server into a static binary first, and then copying that over to the final image. This would let you source the runtime stage
FROM alpine
rather than thenode:18-alpine
base image, which I hypothesized might make the final image size a lot smaller, since it doesn't need to include anything else related to Node.js development (likenpm
orcorepack
) which might be included in a general Node.js base image.Other deployment methods may also benefit from this approach. For example, Tauri currently doesn't support the Next.js server in production, but it does support bundling a Node.js sidecar application to run, which theoretically could include a web server. This means it may be possible to run a "fully-featured" Next.js app as the frontend to a Tauri native application, if the server could be compiled to a binary...
Additional information
Not sure if anyone else has tried this before, or whether anyone went down this path and found out it really doesn't matter either way...I can see that because the whole bun/node runtime needs to be included in the executable, so maybe it's just a negligible improvement?
Example
Here's what the (slightly edited for brevity) Dockerfile might look like if this was possible:
And here's what it might look like using Bun:
Beta Was this translation helpful? Give feedback.
All reactions