seo-blog

How to Fix next/image Silent Failure in Next.js Standalone Docker Deployments

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 Root Cause: Why next/image Fails in Standalone Docker

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:

  1. Sharp is dropped: The .next/standalone directory is created without the sharp package.
  2. Silent fallback: In production, the standalone server starts. Since sharp is missing, it falls back to the default squoosh optimizer or skips optimization entirely.
  3. Cache directory issues: Even if 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.

Step-by-Step Fix: Installing sharp in the Docker Runner Stage

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"]

Verifying That next/image is Using sharp in Production

To verify that your Docker container is successfully using sharp for image optimization, check the container logs and network response headers.

1. Check Container Logs on Startup

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.

2. Inspect the HTTP Headers

  1. Open the network tab in your browser Developer Tools.
  2. Select an optimized image resource (usually requested via /_next/image?url=...).
  3. Check the Response Headers:
    • 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).

FAQ

Why is next/image not optimizing images in my Docker container?

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.

How do I check if my Docker Next.js container is using sharp?

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.

Where are optimized images cached in Next.js standalone mode?

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.

Do I need to register sharp in next.config.js?

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.


Conclusion

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.


Official Sources