dockernext.jsdocker-compose

How do I resolve a Next.js static app Dockerfile build error, "Error: Cannot find module [X]" error?


I'm working to create a production Next.js static docker image after successfully setting up a dev docker environment. I'm having a growing list of challenges working through the build. I've followed other related posts that I could find with no luck yet.

Currently, when I run:

# clean out related images for a fresh start
docker rmi -f $(docker images -a -q)

# build and run
docker-compose -f docker-compose.prod.yaml up --build

I see this error:

=> ERROR [ui builder 6/6] RUN npm run build                                                     3.0s
------
 > [ui builder 6/6] RUN npm run build:
0.458 
0.458 > ui@0.1.0 build
0.458 > next build
0.458 
0.803 Attention: Next.js now collects completely anonymous telemetry regarding usage.
0.803 This information is used to shape Next.js' roadmap and prioritize features.
0.803 You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
0.803 https://nextjs.org/telemetry
0.803 
0.841   ▲ Next.js 14.2.4
0.841 
0.872    Creating an optimized production build ...
2.913 Failed to compile.
2.913 
2.913 src/app/layout.tsx
2.913 An error occurred in `next/font`.
2.913 
2.913 Error: Cannot find module 'tailwindcss'
2.913 Require stack:
2.913 - /home/node/app/node_modules/next/dist/build/webpack/config/blocks/css/plugins.js
2.913 - /home/node/app/node_modules/next/dist/build/webpack/config/blocks/css/index.js
2.913 - /home/node/app/node_modules/next/dist/build/webpack/config/index.js
2.913 - /home/node/app/node_modules/next/dist/build/webpack-config.js
2.913 - /home/node/app/node_modules/next/dist/build/webpack/plugins/next-trace-entrypoints-plugin.js
2.913 - /home/node/app/node_modules/next/dist/build/collect-build-traces.js
2.913 - /home/node/app/node_modules/next/dist/build/index.js
2.913 - /home/node/app/node_modules/next/dist/cli/next-build.js
2.913     at Module._resolveFilename (node:internal/modules/cjs/loader:1090:15)
2.913     at /home/node/app/node_modules/next/dist/server/require-hook.js:55:36
2.913     at Function.resolve (node:internal/modules/helpers:125:19)
2.913     at loadPlugin (/home/node/app/node_modules/next/dist/build/webpack/config/blocks/css/plugins.js:49:32)
2.913     at /home/node/app/node_modules/next/dist/build/webpack/config/blocks/css/plugins.js:157:56
2.913     at Array.map (<anonymous>)
2.913     at getPostCssPlugins (/home/node/app/node_modules/next/dist/build/webpack/config/blocks/css/plugins.js:157:47)
2.913     at async /home/node/app/node_modules/next/dist/build/webpack/config/blocks/css/index.js:124:36
2.913     at async /home/node/app/node_modules/next/dist/build/webpack/loaders/next-font-loader/index.js:86:33
2.913     at async Span.traceAsyncFn (/home/node/app/node_modules/next/dist/trace/trace.js:154:20)
2.913 
2.914 
2.914 > Build failed because of webpack errors
------
failed to solve: process "/bin/sh -c npm run build" did not complete successfully: exit code: 1

This reads as a permissions issue to me or files not getting copied over, yet I'm not quite understanding what I need to do for this build yet.

Here's /ui/Dockerfile.prod:

# Build app
FROM node:19 AS builder

# Set working directory
WORKDIR /home/node/app

# Copy package.json and package-lock.json and install dependencies
COPY package.json package-lock.json* ./
RUN npm ci

# Copy the rest of the application files
COPY . .

# Build the Next.js app
RUN npm run build

# Prod image
FROM node:19 AS runner

# Set working directory
WORKDIR /home/node/app

# Copy built files from builder stage
COPY --from=builder /home/node/app/.next ./.next
COPY --from=builder /home/node/app/public ./public
COPY --from=builder /home/node/app/node_modules ./node_modules
COPY --from=builder /home/node/app/package.json ./package.json

# Set environment variables
ENV NODE_ENV=production
ENV PORT=3000

# Expose the port
EXPOSE 3000

# Start the Next.js app
CMD ["npm", "start"]

docker-compose.prod.yaml:

version: '3.8'

services:
    postgres:
        container_name: postgres-prod
        image: postgres:16
        ports:
            - '5432:5432'
        volumes:
            - ./postgresdata:/var/lib/postgresql/data
            - ./src/migrations/dbinit.sql:/docker-entrypoint-initdb.d/dbinit.sql
        restart: always
        environment:
            POSTGRES_USER: ${POSTGRES_USER}
            POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
            POSTGRES_DB: ${POSTGRES_DB}
        networks:
            - app-network

    api:
        container_name: api-prod
        image: 'node:19'
        build:
            context: ./server
            dockerfile: Dockerfile.prod
        user: 'node'
        working_dir: /home/node/app
        environment:
            - 'NODE_ENV=${NODE_ENV}'
            - 'PORT=3000'
            - 'DATABASE_URL=${DATABASE_URL}'
        expose:
            - '3000'
        ports:
            - '3000:3000'
        command: 'npm run dev:migrate:start'
        networks:
            - app-network
        depends_on:
            - postgres

    ui:
        container_name: ui-prod
        build:
            context: ./ui
            dockerfile: Dockerfile.prod
        user: 'node'
        working_dir: /home/node/app
        environment:
            - 'NODE_ENV=production'
            - 'NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}'
            - 'NEXT_PUBLIC_SOCKET_URL=${NEXT_PUBLIC_SOCKET_URL}'
        ports:
            - '8080:8080'
        command: 'npm start'
        networks:
            - app-network
        depends_on:
            - api

    nginx:
        build:
            context: ./nginx
            dockerfile: Dockerfile.prod
        container_name: nginx-prod
        restart: always
        tty: true
        ports:
            - '80:80'
        networks:
            - app-network
        depends_on:
            - api
            - ui

networks:
    app-network:
        driver: bridge

/ui/package.json:

{
  "name": "ui",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev -p 8080",
    "build": "next build",
    "start": "next start -p 8080",
    "lint": "next lint"
  },
  "dependencies": {
    "@auth0/auth0-react": "^2.2.4",
    "next": "14.2.4",
    "react": "^18",
    "react-dom": "^18",
    "socket.io-client": "^4.7.5"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "eslint": "^8",
    "eslint-config-next": "14.2.4",
    "next-image-export-optimizer": "^1.14.1",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

I'm running Windows 11 Home, Docker Desktop, initiating build via WSL/Ubuntu.

(note: I noticed that I need to adjust ports, yet that's unrelated).

How do I resolve the Next.js static app Dockerfile build error, "Error: Cannot find module 'tailwindcss'" error?


Solution

  • You've corrupted your local Node image. In the Compose file, delete the line:

    services:
      api:
        image: 'node:19'  # <-- DELETE
    

    and manually run

    docker pull node:19
    

    and your build should work again.

    When your Compose file has both build: and image:, Compose builds the image as described, and then tags it with the specified image: name. In this setup, you're replacing your local copy of node:19 with the final output of the build; that sets NODE_ENV=production. When you rebuild it the second time, it starts from the results of the previous build (it does not re-pull the original image), and with NODE_ENV set, it only installs production dependencies.

    You should only set build: and image: together if you're planning to push the image to a remote registry like Docker Hub. In that case the image: is the name of the result image, not the base image.

    Many of the other settings in your Compose file either override things that are in the Dockerfile (user:, expose:, command:, environment: [NODE_ENV=...]) or are things Compose can set itself (container_name:, networks:). You can significantly simplify the Compose file by deleting these as well.