DEV Community

Ango Jeffrey
Ango Jeffrey

Posted on

Optimizing Next.js Docker Images with Standalone Mode

Recently, I was discussing with the DevOps engineer at my company and he asked me why the Docker image for the Next.js frontend application was so large, we're talking over 2GB large, and I didn't have a good answer to that, I knew that because we were taking advantage of some Next.js features like middleware and redirects, we had to run the app as a server, which meant we couldn't serve the pages and assets as static exports, That would naturally make the image larger than a static export, but 2GB still felt excessive.
So, over the weekend I did a deep dive researching for possible causes for bloated Docker images and how to reduce the file size. After quite a bit of research and trial and error, I came across a very useful (and surprisingly under-discussed) feature: Next.js Standalone Mode.

What Is Standalone Mode?

Standalone mode strips out the irrelevant parts of your application from your build, such as unused node_modules files, unused component files and folders, giving you only the bare bones files needed to make your app work.

Implementing Standalone Mode Builds

To enable standalone mode you simply add this to your next.config.js:

const nextConfig: NextConfig = {
output: "standalone",

  /* ...other configurations */
}
Enter fullscreen mode Exit fullscreen mode

Next, go to your Dockerfile and implement a multi stage build process to keep your image lean and efficient:

# Stage 0: Base image definition (optional, but good for consistency)
FROM node:18-alpine AS base

# Stage 1: Dependencies Installation
FROM base AS deps

RUN apk add --no-cache libc6-compat
WORKDIR /app

# Install dependencies (including devDependencies needed for the build)
COPY package.json yarn.lock* ./
RUN yarn --frozen-lockfile --prefer-offline --no-audit 

# Stage 2: Application Build
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY ./ ./ 

# build app
RUN yarn build

# Stage 3: Production Runner
FROM node:18-alpine AS runner

USER node

WORKDIR /app

COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public


EXPOSE 3000

CMD ["node", "server.js"]
Enter fullscreen mode Exit fullscreen mode

Note: The server.js file is generated automatically in the .next/standalone directory when using standalone mode.

Why this works

This Docker image uses a multi-stage build process to package a Next.js app in a way that’s both efficient and production-ready. It starts with a lightweight Node.js Alpine image and installs the app’s dependencies in a separate stage to keep things clean. Then, during the build stage, it compiles the app using yarn build, taking advantage of Next.js’s standalone mode, which bundles only the files and dependencies the app actually needs to run. Finally, the runner stage creates a clean, production ready image by copying only the standalone output (.next/standalone, .next/static, and public) into a fresh Node.js Alpine image. It sets the working directory, switches to a non-root node user for security, and runs the app with node server.js. By avoiding unnecessary files like full source code and unused node_modules, we end up with a much smaller image that’s faster to build, deploy, and run without sacrificing any of the dynamic features Next.js offers.

🚀The Results

Opting into Next.js standalone mode reduced our docker image size by over 90%, from over 2GB to less than 200MB!
Crazy right? Standalone mode is a nifty, underrated feature that deserves way more attention-especially if you're deploying server-rendered web apps with Docker.

Top comments (0)