I have a NextJS project with the following lines in my package.json
:
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
},
All three scripts run without any hiccups from my command line. Now I’m trying to dockerize my application. My definitions are in a Dockerfile.prod
:
# Final stage
# --------------------------------------------------------
# Install Node base image with Alpine Ubuntu
FROM node:20.11-alpine AS builder
# Set the working directory inside the container
WORKDIR /app
# Install pnpm globally in the builder stage
RUN apk add --no-cache bash
RUN npm install -g pnpm
# Copy package.json and pnpm-lock.yaml
COPY pnpm-lock.yaml package.json ./
# Install dependencies using pnpm
RUN pnpm install
# Copy the rest of the files to the working directory
COPY . .
# Generate Prisma Client
RUN npx prisma db push
RUN npx prisma generate
RUN pnpm build
# Runner stage
# --------------------------------------------------------
# Install Node base image with Alpine Ubuntu
FROM node:20.11-alpine AS runner
# Set the working directory inside the container
WORKDIR /app
# Install pnpm globally in the builder stage
RUN apk add --no-cache bash
RUN npm install -g pnpm
# Copy only the necessary files from the builder stage
COPY --from=builder /app/next.config.js /app/.next /app/public /app/node_modules ./
# Expose port 3000 from the container
EXPOSE 3001
# Start the Next.js server
CMD ["pnpm", "start"]
Besides, I also have a docker-compose.yaml
that looks like this:
version: "3.8"
services:
poco-production:
build:
context: .
dockerfile: Dockerfile.prod
container_name: poco-web-prod
ports:
- "3001:3001"
volumes:
- .:/app
- /app/node_modules
env_file:
- .env
Now when I build this image using docker-compose up --build
, I get the following error:
Attaching to poco-web-prod poco-web-prod | poco-web-prod | > poco@0.1.0 start /app poco-web-prod | > next start poco-web-prod | poco-web-prod | sh: next: not found poco-web-prod | ELIFECYCLE Command failed.
What am I missing here? ChatGPT says this:
The error message you’re encountering—
sh: next: not found
—indicates that thenext
command is not being found within your container.
But that can’t be true because this error only comes at next start
while next build
runs just fine. I am also copying my node_modules
from the build
stage into the runner
stage. What else could be missing?
UPDATE
As suggested by Ahmed Abdelbaset in his answer below, I updated my Dockerfile to this:
# Install Node base image with Alpine Ubuntu
FROM node:20.11-alpine AS base
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
# Copy dependency lists
COPY package.json pnpm-lock.yaml* ./
# Enable corepack for pnpm
RUN corepack enable pnpm
# Install dependencies using pnpm
RUN pnpm i --frozen-lockfile
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Generate Prisma Client
RUN npx prisma db push
RUN npx prisma generate
# Enable corepack for pnpm and install dependencies
RUN corepack enable pnpm && pnpm build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Disable NextJS telemetry during runtime
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# 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
# set hostname to localhost
ENV HOSTNAME "0.0.0.0"
# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD ["node", "server.js"]
And added output: "standalone"
to my next.config.js
as follows:
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
config.resolve.alias.canvas = false
config.resolve.alias.encoding = false
return config
},
images: {
remotePatterns: [
{
protocol: "https",
hostname: "lh3.googleusercontent.com",
pathname: "**",
},
],
},
output: "standalone",
}
module.exports = nextConfig
This is now throwing a different error:
Cannot find module '/app/server.js'
The error message you’re encountering—sh: next: not found—indicates that the next command is not being found within your container.
That's true, before the pnpm build
(next build
) you have pnpm install
which generates node_modules
. But before pnpm start
(next start
), You don't have node_modules
.
COPY --from=builder /app/next.config.js /app/.next /app/public /app/node_modules ./
The previous line will copy the contents of the sources not the directories themselves. The output will not be:
- next.config.js
- .next/
- static
- server
- ...
- public
- ...
- node_modules
- .pnpm
- next
- ...
But it will be
- next.config.js
- static
- server
- .pnpm
- ...
so, there is no node_modules
.
Next.js provides a standalone
output the collect all necessary files to run output in Docker in one place. To enable, configure next.config.js
module.exports = {
output: "standalone",
}
After running next build
, you'll have .next/standalone
dir that is stand alone in itself - no need to any other directory.
# Runner stage
# --------------------------------------------------------
# Install Node base image with Alpine Ubuntu
FROM node:20.11-alpine AS runner
# Set the working directory inside the container
WORKDIR /app
# Copy only the necessary files from the builder stage
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/static ./static
# Expose port 3001 from the container
EXPOSE 3001
# Start the Next.js server
CMD ["node", "server.js"]
During running the server, Next.js will write/read .next
this might conflict with the docker permissions. If that happened you will need
# Runner stage
# --------------------------------------------------------
# Install Node base image with Alpine Ubuntu
FROM node:20.11-alpine AS runner
# Set the working directory inside the container
WORKDIR /app
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
# Copy only the necessary files from the builder stage
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./static
USER nextjs
# Expose port 3001 from the container
EXPOSE 3001
# Start the Next.js server
CMD ["node", "server.js"]