How do I Resolve a Next.js Static App Docker Build Error

I’m building a static Next.js site inside Docker and hit the dreaded

Error: Cannot find module 'tailwindcss'
Require stack: …next/dist/build/webpack/config/blocks/css/plugins.js

Below is the exact path I took from panicroot causerepeatable fix plus a few extra tweaks you can practise once the build works.

I Saw The Error

Inside a multistage Dockerfile I run:

npm run build

and the compiler stops cold with that Tailwind message. Next.js’ font loader calls PostCSS → PostCSS looks for the Tailwind plugin → Node can’t require('tailwindcss') build fails. Because it fails while compiling, Tailwind is clearly a build-time dependency, not a run-time one.

Why it Happens

npm ci + NODE_ENV=production

npm ci installs what’s in package-lock.json.
If the env var NODE_ENV is already production, npm ci silently skips everything in devDependencies. My docker-compose.prod.yaml sets NODE_ENV=production early, so Tailwind never makes it into the image.

Multi Stage Rules

Even if I did sneak Tailwind into the builder stage, I only copy .next, public, and node_modules into the final stage. That’s fine—but every module the compiler needs must exist before next build runs.

Explain Code

Fix A keep Tailwind in devDependencies, force-install dev packages in the builder

# --- builder stage ---
FROM node:20-alpine AS builder

WORKDIR /app
ENV NODE_ENV=development # ← dev deps WILL be installed
COPY package*.json ./
RUN npm ci --include=dev # npm v9 flag (or: npm install)

COPY . .
RUN npm run build

# Strip dev deps to keep the final layer slim
RUN npm prune --omit=dev && npm cache clean --force

Promote Tailwind to regular dependencies

install tailwindcss postcss autoprefixer --save

With Tailwind now in "dependencies", the original Dockerfile works because those modules install even when NODE_ENV=production.

My Safer, Slimmer Multistage Dockerfile

############################
# 1) Builder #
############################
FROM node:20-alpine AS builder

WORKDIR /app
# 1-A copy only lockfile & manifest for cache efficiency
COPY package.json package-lock.json* ./
RUN npm ci --include=dev # all build-time tools present

# 1-B copy source and build
COPY . .
RUN npm run build && npx next telemetry disable

# 1-C remove dev bits
RUN npm prune --omit=dev

############################
# 2) Runner #
############################
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=8080

# copy only what the server needs
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json .

EXPOSE 8080
CMD ["node_modules/.bin/next", "start", "-p", "8080"]

Checklist Code

#Add thisWhy I like it
1Bundle Analyzer (npm run analyze)Find and shrink huge JS chunks before shipping
2Buildx cache (docker buildx build --cache-to=type=inline)Big cuts in CI build time
3next export stageServe pure HTML from any CDN—no Node needed
4Health-check route (/healthz + `HEALTHCHECK CMD curl -f
5Tailwind JIT purge (NEXT_PUBLIC_TAILWIND_MODE=build)Final CSS often < 10 KB

Pick one, rebuild with:

compose --profile prod up --build

and watch size and speed improve.

Define a Code

  1. Decide where a package lives. Build-time → devDependencies, run-time → dependencies.
  2. Keep NODE_ENV out of the builder stage unless I truly need it.
  3. Prune dev deps after the build or copy only the compiled output.
  4. Cache smartly—copy package*.json first, source code later.
  5. Silence telemetry if I don’t want extra outbound calls (npx next telemetry disable).

Final Thought

Once I realised the container simply never got Tailwind in the first place, the fix was trivial: install the right packages at the right stage and keep the runtime image lean. Follow the checklist above and the “Cannot find module ‘tailwindcss’” error (or any cousin of it) stays gon for good.

Related blog posts