node.jsdockernext.jsnode-modules

Standalone build does not create node_modules folder, but only in Docker container


I am using Next.js 15.3.1 on Node.js 22.16. I have the Next.js project configured as a standalone build. When I build the application using npm run build, a ./.next/standalone folder is created, containing server.js, package.json, a .next folder, and a node_modules folder containing dependencies used by my application. Starting the server with node server.js works perfectly as expected.

I am trying to create a Docker container for this application, but I'm running into a strange issue where the node_modules folder doesn't get created on build. This makes it impossible to start this server. The package.json gets created, so if I add another npm install with --omit=dev to the Dockerfile, it works perfectly fine. But I think this is not ideal, as it those dependencies should have already been installed from the build, and that just means they won't be cached and will be reinstalled whenever the source code changes. It also probably makes my Docker image larger than it needs to be.

This is my Dockerfile:

# Install build dependencies
FROM node:22.16-bookworm AS dependencies
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

# Build application
FROM node:22.16-bookworm AS builder
WORKDIR /app

ENV NEXT_TELEMETRY_DISABLED=1

# Copy source code and build
COPY --from=dependencies /app/node_modules ./node_modules
COPY package.json eslint.config.mjs tsconfig.json ./
COPY . ./frontend
RUN npm run build frontend

# The runtime environment
FROM node:22.16-slim AS runtime
WORKDIR /app

ENV NEXT_TELEMETRY_DISABLED=1

# Copy build results, leveraging output traces to reduce image size
COPY --from=builder /app/frontend/.next/standalone /app
COPY --from=builder /app/frontend/public ./public
COPY --from=builder /app/frontend/.next/static ./.next/static

EXPOSE 3000
ENV HOSTNAME="0.0.0.0"
ENV PORT=3000

# Start application
CMD ["node", "server.js"]

It has three phases, one to install the dependencies, one to actually build the standalone app, and then one to take the built app and actually start it. The node_modules should be in the standalone folder, but they aren't. So I have to add that RUN npm install --omit=dev line after the COPY commands.

When I run docker build, the image is created successfully. It's only when I launch a container that it crashes, because it can't find the module next --- there is no node_modules folder.


Solution

  • The issue was how I was copying my file structure in this part:

    # Copy source code and build
    COPY --from=dependencies /app/node_modules ./node_modules
    COPY package.json eslint.config.mjs tsconfig.json ./
    COPY . ./frontend
    RUN npm run build frontend
    

    I was changing the application structure to try and help with caching, but this wasn't actually necessary and was just causing too many problems. Updating it to this:

    # Copy source code and build
    COPY --from=dependencies /app/node_modules ./node_modules
    COPY . .
    RUN npm run build
    

    and changing the rest of the Dockerfile accordingly resolved the issue.