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 seeking guidance on the recommended pattern for creating a single, optimized Docker image for a Payload + Next.js application that can be used for two distinct purposes in a serverless environment (e.g., Google Cloud Run, AWS Fargate):
Web Server: Running the Next.js application via node server.js.
Job Worker: Running background jobs via payload jobs:run.
The goal is to use the output: 'standalone' feature in Next.js to create a minimal, fast-starting production image, but this conflicts with the needs of the Payload CLI for running jobs.
The Core Problem
The Next.js standalone output is highly optimized for running the web server. It traces dependencies and only includes the node_modules necessary for the server code. This correctly excludes CLI-only dependencies like tsx, which are required by payload/bin.js.
This leads to two conflicting approaches:
Approach 1: The Optimized Standalone Image (Fails for Workers)
This uses the standard multi-stage Dockerfile recommended in the documentation. It produces a small and efficient image.
Running CMD ["node", "server.js"] works perfectly for the web server.
However, changing the command to CMD ["node", "node_modules/payload/bin.js", "jobs:run"] fails with Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'tsx'.
Attempts to fix this by adding tsx and payload to experimental.outputFileTracingIncludes in next.config.mjs didn't work.
# Dockerfile using 'standalone' output# ... (deps and builder stages are standard)# Production imageFROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
# ... (user setup)# Copy only the minimal standalone outputCOPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
# This works for the web server:# CMD ["node", "server.js"]# This fails for the worker due to missing dependencies:CMD ["node", "node_modules/payload/bin.js", "jobs:run"]
Approach 2: The Full, Unoptimized Image (Works, but is large)
This approach avoids the standalone output and copies the entire built application, including the full node_modules directory.
This works for both the web server and the job runner.
However, it defeats the purpose of standalone optimization, resulting in a significantly larger image and slower startup times.
# ... (deps and builder stages are standard)# Production imageFROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
# ... (user setup)# Copy the entire application, not just standaloneCOPY --from=builder --chown=nextjs:nodejs /app ./
USER nextjs
# Both of these commands now work, but the image is not optimized# CMD ["node", "server.js"]# CMD ["node", "node_modules/payload/bin.js", "jobs:run"]
Desired outcome
I'm looking for a best-practice solution to have a single, optimized Docker image that can be deployed as both a web server and a one-off job runner.
Any guidance on this common serverless pattern would be greatly appreciated. Thank you!
Payload Version
3.54.0
Node.js Version
22.19.0
Dockerfile
# To use this Dockerfile, you have to set `output: 'standalone'` in your next.config.mjs file.
# From https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile
FROM node:22.19.0-alpine AS base
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN \
if [ -f yarn.lock ]; then yarn run build; \
elif [ -f package-lock.json ]; then npm run build; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
else echo "Lockfile not found." && exit 1; \
fi
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
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
# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD HOSTNAME="0.0.0.0" node server.js
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.
-
I'm seeking guidance on the recommended pattern for creating a single, optimized Docker image for a Payload + Next.js application that can be used for two distinct purposes in a serverless environment (e.g., Google Cloud Run, AWS Fargate):
node server.js
.payload jobs:run
.The goal is to use the
output: 'standalone'
feature in Next.js to create a minimal, fast-starting production image, but this conflicts with the needs of the Payload CLI for running jobs.The Core Problem
The Next.js
standalone
output is highly optimized for running the web server. It traces dependencies and only includes thenode_modules
necessary for the server code. This correctly excludes CLI-only dependencies liketsx
, which are required bypayload/bin.js
.This leads to two conflicting approaches:
Approach 1: The Optimized Standalone Image (Fails for Workers)
This uses the standard multi-stage
Dockerfile
recommended in the documentation. It produces a small and efficient image.CMD ["node", "server.js"]
works perfectly for the web server.CMD ["node", "node_modules/payload/bin.js", "jobs:run"]
fails withError [ERR_MODULE_NOT_FOUND]: Cannot find package 'tsx'
.tsx
andpayload
toexperimental.outputFileTracingIncludes
innext.config.mjs
didn't work.Approach 2: The Full, Unoptimized Image (Works, but is large)
This approach avoids the
standalone
output and copies the entire built application, including the fullnode_modules
directory.standalone
optimization, resulting in a significantly larger image and slower startup times.Desired outcome
I'm looking for a best-practice solution to have a single, optimized Docker image that can be deployed as both a web server and a one-off job runner.
Any guidance on this common serverless pattern would be greatly appreciated. Thank you!
Payload Version
3.54.0
Node.js Version
22.19.0
Dockerfile
Beta Was this translation helpful? Give feedback.
All reactions