DEV Community

Peon Sh
Peon Sh

Posted on

Next.js Standalone Mode: Small Docker Images That Boot Fast

output: "standalone" cuts Next.js images from 1 GB+ to ~150 MB. How it works, the static-files gotcha, and a copy-paste Dockerfile.

The problem standalone solves
A naive Next.js Dockerfile copies the entire project, node_modules included, into the final image: 1 GB or more, most of it build tooling and dev dependencies the production server never touches. Every deploy moves that gigabyte, every host stores copies of it, and cold starts pay for loading it.

With output: "standalone" in next.config, next build performs file tracing: it walks the actual require/import graph of the production server and emits .next/standalone, a self-contained folder with server.js and only the node_modules files genuinely reached at runtime. Typical result: 120 to 180 MB final images, an 85 to 90% reduction.

// next.config.js
module.exports = { output: 'standalone' };

The gotcha everyone hits once
Standalone output deliberately excludes two directories: .next/static (hashed JS/CSS assets) and public/ (your static files). The assumption is you might serve them from a CDN. Self-hosting them means copying both into the image yourself, forget this, and the app boots fine but every page loads without styles or scripts, assets 404ing:

FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static # <- required
COPY --from=build /app/public ./public # <- required
EXPOSE 3000
CMD ["node", "server.js"]

Environment variable timing
The other classic standalone-mode bug is env timing. NEXT_PUBLIC_* variables are inlined into the client JavaScript at build time; setting them at runtime does nothing, they must exist during npm run build (build args or platform build-time variables). Server-only secrets are the reverse: read at runtime from process.env, so they belong in runtime environment variables and never need rebuilds. The symptom of mixing these up is always "works locally, undefined in production".

What still works (everything)

  • ISR: revalidation runs in the server process; cache lives on the container filesystem (fine for one instance; use a custom cache handler when scaling out)
  • next/image: on-demand optimization works out of the box, sharp is bundled by tracing
  • Middleware, API routes, server actions: all present, this is the full Next.js server, not an adaptation
  • The only external assumption gone: no CDN implied; add Cloudflare in front if you want edge asset caching

Payoff in production
Concrete numbers from typical apps: image 1.1 GB to 150 MB, build-and-deploy cycle minutes to under one, container start under a second, and far less disk churn on deploy-heavy hosts (relevant when your platform builds on the server, as Peon does, layer cache stays warm and rebuilds move only your app layer). Standalone mode is the single highest-leverage line of configuration in self-hosted Next.js.

Top comments (0)