DevOps

Next.js builds locally, fails in Docker: fix the image error

The exact same source code compiles on your laptop and crashes in the container because the container runs a Node major version your local shell does not — add sharp and the crash disappears.

Mahdi Ben RhoumaMahdi Ben Rhouma
9 min read

TL;DR#

next build crashes inside the container with TypeError: Failed to parse URL from .../mozjpeg_node_dec.wasm while the identical source builds fine on your laptop. The cause is a statically imported image (import pic from '../assets/pic.jpg'): Next.js 12's bundled JPEG decoder (squoosh/mozjpeg) calls fetch() on a plain filesystem path, and Node 18's native fetch() — unlike the polyfill your local Node version may be using — refuses anything that is not a valid URL. The fix is to add sharp as a project dependency: Next then uses it instead of the bundled decoder, and the exact same Dockerfile and image build successfully.

text
Failed to compile.
 
./assets/pic.jpg
TypeError: Failed to parse URL from /app/node_modules/next/dist/server/lib/squoosh/mozjpeg/mozjpeg_node_dec.wasm
    at node:internal/deps/undici/undici:12637:11
 
Import trace for requested module:
./pages/index.js
 
> Build failed because of webpack errors

This is the exact error from a real Stack Overflow question: a Next.js app that built locally but failed inside Docker with this trace, on a Dockerfile based on node:18-alpine3.14.

Why it happens#

Next.js statically imports of images (import pic from '../assets/pic.jpg') are not just copied — the build also decodes the image to generate a placeholder. Up to Next.js 12, that decoding went through a WebAssembly JPEG decoder bundled under next/dist/server/lib/squoosh, with no external dependency required. sharp was never a listed dependency of next@12.1.6 itself (confirmed by its published package.json on the npm registry: dependencies lists only @next/env, caniuse-lite, postcss and styled-jsx — no sharp); it is only require()-d if the project added it on its own.

The squoosh decoder's instantiateAsync() function calls fetch() on a plain filesystem path to load its .wasm binary. That worked for years because, on the Node versions available when this code was written, no native global fetch existed, and Next's own bundled node-fetch-style polyfill was lenient about the input it accepted. Node 18 made fetch() a built-in global, backed by undici, which is strict: fetch() requires a real URL (http:, https: or file:), and a bare path like /app/node_modules/next/dist/server/lib/squoosh/mozjpeg/mozjpeg_node_dec.wasm fails to parse as one — hence TypeError: Failed to parse URL from ….

This is not Docker-specific and not Alpine-specific: the same trace is reported in vercel/next.js#38020, reproduced by the reporter on macOS with Node 18, where the identical build succeeds on Node 17 and fails on Node 18 — no container involved. What makes it look like a Docker problem is that a Dockerfile usually pins an exact base image (FROM node:18-alpine3.14), while the host machine runs whatever Node version its own shell has active. The build only breaks wherever Node 18 (or later) is actually running — which, for most readers of this exact error, is the container.

Fix#

1. Add sharp as a direct dependency#

bash
npm install sharp
# or: yarn add sharp

Next.js checks for sharp and, when present, uses it for image handling instead of the bundled squoosh decoder — current Next.js versions document sharp as a required dependency for the built-in Image Optimization path for this reason. One Stack Overflow answer to this exact question (score 9) points at the same GitHub issue and names npm i sharp as the resolution.

Verified by reproduction: building the reporter's own Dockerfile (FROM node:18-alpine, COPY . /app, npm install, npm run build) against a minimal next@12.1.6 project with one statically imported JPEG reproduces TypeError: Failed to parse URL from …/mozjpeg_node_dec.wasm exactly. Adding sharp as a dependency in package.json — no other change, same Dockerfile, same base image, same image file — turns the build into info - Compiled successfully (reproduced 16 October 2026, Node 18.20.8 inside the official node:18-alpine image).

2. If sharp fails to install on Alpine, add its native build dependencies#

sharp ships prebuilt binaries for common platforms, but when none matches (an older sharp version or an uncommon CPU architecture), it compiles libvips from source, which needs a C toolchain. The accepted answer to the Stack Overflow question fixed this by switching the base image and installing those packages before yarn install:

dockerfile
FROM node:lts-alpine3.14
 
RUN apk update && \
    apk add --no-cache libc6-compat autoconf automake libtool make tiff jpeg zlib zlib-dev pkgconf nasm file gcc musl-dev
 
RUN npm install -g npm
ENV NEXT_TELEMETRY_DISABLED 1
ENV NODE_ENV production
ENV PORT 3000
EXPOSE 3000
 
COPY . /app
WORKDIR /app
RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /app
USER appuser
 
RUN yarn install
RUN yarn build
ENTRYPOINT [ "sh", "entrypoint.sh" ]

If your project already has a working, minimal Alpine toolchain set up for other native modules, start with step 1 alone — most current sharp releases ship prebuilt musl binaries and do not need a full build toolchain on Alpine.

3. Longer term: move off Next.js 12#

Next.js removed the bundled squoosh decoder entirely in later major versions — a code search of the current vercel/next.js repository returns zero matches for squoosh. Upgrading past Next.js 12 removes this failure mode at the source rather than papering over it with an extra dependency, but that is a larger migration than this fix and outside what this question asked.

Verify the fix#

Run the same Dockerfile again, from a clean build context, and confirm the log no longer mentions squoosh or mozjpeg:

bash
docker build --no-cache -t my-next-app .

The build output should move past Creating an optimized production build straight to Compiled successfully and the page summary table, with no Failed to compile block. If the error now changes to something else entirely — the container can find the build but the server won't start, reporting a missing .next/BUILD_ID — check that the stage that runs next build and the stage that runs next start share the same /app working directory and distDir; a multi-stage Dockerfile that copies the wrong folder across stages produces exactly that follow-on error.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

Variants: when the image lives behind a different Dockerfile shape#

Readers hit the same crash in slightly different setups, all sharing the one root cause above:

  • COPY before npm install vs. after. This error has nothing to do with copy order (it happens during the RUN npm run build step, with the image file already present), unlike missing-module errors in multi-stage Docker builds, which really are caused by a bad COPY sequence or a mismatched tsc output path. Do not confuse the two when debugging a failing Dockerfile — check which stage and which command actually throws first.
  • entrypoint.sh running yarn start. The original Dockerfile forwards control to entrypoint.sh via ENTRYPOINT, which never runs if the preceding RUN yarn build step already failed — Docker stops the image build at the first failing RUN. If you need the entrypoint script to pick up variables injected with docker run -e, remember that RUN executes only at build time; see running a shell command before ENTRYPOINT for that separate, unrelated pitfall.
  • Host and image Node versions drift apart over time. If your Dockerfile and your local nvm/fnm version drift apart over months, this class of "works locally, fails in the container" surprise returns under a different name — the check is the Node major version comparison in the closing paragraph below. If you also want to run the dev loop itself in containers, a Docker Compose dev setup covers bind-mounting the source, keeping node_modules in a named volume so the host copy doesn't clobber the container's, and watch mode for reloads.

The underlying lesson holds beyond this one error: when a Next.js build only fails inside Docker, compare the Node major version the Dockerfile pins against the one your shell is actually running before assuming the container or the base distribution is at fault — they were, in this case, innocent.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote