When deploying Next.js applications in production using Docker, developers often notice their pages loading slowly or page weight bloating dramatically. The culprit is almost always next/image silently failing to optimize images, falling back to serving the original, unoptimized source files.
In output: 'standalone' mode, Next.js uses an automated dependency tracer to pack the server bundle. Because the image optimization library sharp is resolved dynamically at runtime, the tracer drops it from the standalone build. Without sharp, Next.js defaults to a slow JavaScript-based image resizer (which causes high CPU spikes) or silently serves raw files.
This guide explains the root cause of the Next.js standalone image optimization failure and provides a step-by-step, production-ready Dockerfile fix.
The Next.js standalone tracer (@vercel/nft) analyzes imports at build time to determine which files under node_modules are required in production. It places these files inside the .next/standalone folder.
However, sharp is an optional dependency with native binary bindings. Because Next.js loads it dynamically using a try-catch block (require('sharp')), the build-time dependency tracer cannot guarantee it is needed. As a result:
.next/standalone directory is created without the sharp package.sharp is missing, it falls back to the default squoosh optimizer or skips optimization entirely.sharp is present, the Alpine node user often lacks write permissions to the .next/cache/images directory inside the container, causing optimization cache writes to fail silently.To resolve this issue, you must manually install sharp in the final runner stage of your multi-stage Docker build, and configure the image cache directory write permissions.
Here is the production-ready, highly optimized Dockerfile:
# Stage 1: Install dependencies
FROM node:18-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package*.json ./
RUN npm ci
# Stage 2: Build the application
FROM node:18-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
# Stage 3: Runner stage
FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
# Create a non-root system user and group
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy public directory and static assets
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/static ./.next/static
# Copy the standalone server files
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
# FORCE install sharp in the production runner directory
# This ensures sharp is available to the standalone server.js
RUN npm install sharp
# Create and set permissions for the image cache directory
RUN mkdir -p .next/cache/images && chown -R nextjs:nodejs .next/cache/images
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
To verify that your Docker container is successfully using sharp for image optimization, check the container logs and network response headers.
When you start the container, look at the stdout logs. If sharp is missing, you will see a warning resembling:
Keep in mind, Next.js image optimization requires "sharp" to be installed in your project.
If you do not see this warning, Next.js has successfully detected and loaded the sharp library.
/_next/image?url=...).X-Nextjs-Cache: Should read HIT or MISS (never BYPASS).Content-Type: Should be a modern optimized format like image/webp or image/avif (not the original image/png or image/jpeg).In standalone output mode, Next.js only copies static analysis imports to the production build directory. Since sharp is loaded dynamically, the bundler excludes it. You must explicitly run npm install sharp in the final Docker runner stage.
Check the container logs on startup. If you see the message “Next.js image optimization requires ‘sharp’ to be installed”, it is not loaded. You can also verify via response headers: optimized images will have Content-Type: image/webp or image/avif instead of their original format.
Next.js caches optimized images inside .next/cache/images in the working directory. Ensure this folder exists and is owned by the container runner user (chown -R nextjs:nodejs .next/cache/images), otherwise image optimization will fail silently on every request.
No. You do not need to configure anything in next.config.js to enable sharp. Next.js automatically checks for the package’s presence in your node environment on startup and configures it dynamically.
Resolving image bloat in containerized Next.js applications requires addressing the dynamic loading pattern of the sharp dependency. By ensuring sharp is explicitly installed in the runner stage of your Dockerfile and adjusting directory permissions for the .next/cache/images path, you can guarantee fast loading times and optimized image payloads in production.