Skip to main content

Cloudsoft Zone Build, deploy and scale with practical guides to PHP, Node.js, React, DevOps and AI automation. Explore the guides

Cloudsoft Zone
DevOpsJavaScriptNode.jsTutorial

Next.js multi-stage Docker build: standalone output, non-root runtime and asset checks

Create a Next.js multi-stage Docker image with standalone output, locked dependencies, non-root execution and explicit asset copying. Verify build-time versus runtime variables.

Nilesh Kushvaha

5 min read

Next.js artwork with a multi-stage Docker workflow separating dependency installation, standalone build output and final runtime.
Image: Cloudsoft Zone layout; Vercel / CC BY-SA 4.0
In this article

A Next.js image builds successfully, but production pages load without CSS. Another deployment starts correctly while the browser still calls the staging API. Both problems can occur after the build command has passed.

A Next.js multi-stage Docker build needs a complete runtime artifact. This guide enables Next.js standalone output Docker images, bounds the build context, writes a Next.js Dockerfile with separate dependency and runtime stages, checks native dependencies and separates build-time from runtime configuration.

A Next.js multi-stage Docker build needs a complete runtime artifact and an explicit configuration model. Standalone output reduces what must be shipped, but static files and build-time browser values still need deliberate handling.

This example assumes one npm-managed application at the build-context root, a committed package-lock.json, a public directory and a build script that runs the Next.js production build. Our Node.js production Dockerfile guide covers the general container foundation.

A multi-stage image separates locked dependency installation, the Next.js build and the non-root runtime.
A successful build does not prove the final copied artifact contains every runtime asset.

Step 1: Enable standalone output

For a single application using next.config.mjs, the relevant setting is:

JavaScript
export default {
  output: 'standalone',
};

Merge it into the existing configuration rather than replacing image rules, redirects or other required settings. A monorepo may need outputFileTracingRoot and different copy/start paths because dependencies and the server artifact can sit outside the application directory.

Standalone tracing follows runtime dependencies; dynamically selected files may require explicit tracing configuration. Identify templates, certificates or other runtime data the application reads rather than assuming every file in the repository is included.

Step 2: Bound the build context

Keep local dependencies, build output and real environments out of the context. An illustrative .dockerignore is:

Text
node_modules
.next
.git
.env
.env.*
*.log

Commit non-secret build configuration through the application's documented mechanism. Do not copy the developer's real .env into the image to make a failing build pass.

Input Treatment
Package manifests and lockfile Committed and installed reproducibly
Public browser configuration Explicit build-time or designed runtime contract
Production credentials Supplied to the server at deployment
Local build output Excluded and rebuilt in the image
Required static/public files Explicitly delivered with the artifact

Step 3: Build separate dependency and runtime stages

This Dockerfile uses the Node 24 Debian slim tag family for illustration. The tag is mutable; a production release should record the resolved approved digest and update it deliberately. Use the runtime version supported by the locked application.

Dockerfile
FROM node:24-bookworm-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM node:24-bookworm-slim AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:24-bookworm-slim AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME=0.0.0.0
RUN groupadd --gid 1001 nextjs \
    && useradd --uid 1001 --gid nextjs --no-create-home nextjs
COPY --from=builder --chown=nextjs:nextjs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nextjs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nextjs /app/public ./public
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]

The final stage excludes the general build workspace and runs the standalone server directly. The example requires the public directory; adapt its copy step if the application intentionally has none. Do not switch to next dev in the final stage to avoid a missing artifact.

Step 4: Check native dependencies and writes

Use compatible build and runtime OS families for native modules. Copying native binaries between incompatible environments can fail only after the application begins handling a particular route.

Identify runtime cache or image-optimization paths and give the intended user only the necessary write access. A read-only root filesystem needs an explicit plan for writable caches and temporary files; enabling it blindly can break a working route.

Check required image-optimization dependencies with the installed Next.js version. Do not assume a home page request exercises every native dependency or cache path.

Next.js standalone server output must be paired with public files and static chunks and verified through browser routes.
Monorepos need the correct tracing root and nested output path.

Step 5: Separate build-time and runtime configuration

NEXT_PUBLIC variables inlined into browser code are fixed when the bundle is built. Changing those values when starting a container will not rewrite the already generated JavaScript.

Server-only environment values can be read at runtime where the application uses dynamic server execution. Prerendered output and build-time data access need their own review. A build that reaches a real production database can accidentally embed production content or perform unintended work.

Do not put credentials into public variables, image arguments or committed manifests. If the build needs a private registry credential, use the build system's secret mechanism and ensure the command does not copy it into an artifact.

Step 6: Verify the final image privately

Build and run in an isolated environment. Publish the container port only on a chosen local loopback port for rehearsal; exposing the runtime publicly is not required to verify it.

Shell
docker build --tag next-web:review .
docker run --name next-web-review --rm \
  --publish 127.0.0.1:4300:3000 next-web:review

Supply the application's required server configuration through its protected deployment mechanism. The command alone is sufficient only for applications without required external configuration. Stop the named rehearsal container when finished; do not use global cleanup commands.

Step 7: Check routes, assets and failure behavior

Acceptance case Expected evidence
Server-rendered page Correct content and intended runtime data access
CSS, JavaScript and public image Successful asset responses from the intended path
Optimized image Native dependency and writable-cache behavior work
Authenticated page Correct cookies and private cache boundaries
Missing runtime dependency Clear bounded failure without secret disclosure
Process replacement The selected proxy/deployment lifecycle recovers safely

Inspect the browser network panel as well as server logs. A server status of 200 can coexist with failed chunks or an incorrect public API address.

Build-time public configuration differs from runtime server secrets when promoting a Next.js Docker image.
Never rely on a changed runtime public variable to rewrite an already built browser bundle.

Step 8: Promote an immutable artifact

Record the image digest, lockfile revision, build inputs and acceptance evidence. Reuse that artifact only across environments compatible with its public build-time values and rendering model.

Keep the previous image and compatible runtime configuration for rollback. Database or API contract changes can still prevent a simple image reversal. Our single-VPS deployment guide explains that release boundary.

For a Next.js container and promotion review, contact Cloudsoft Zone.

Featured image: Cloudsoft Zone technical layout with nextjs artwork by Vercel (CC BY-SA 4.0). Product mark resized proportionally for identification; no endorsement implied. Layout: CC BY-SA 4.0.

Frequently asked questions

  • Does standalone output automatically include public and .next/static?

    No. When the standalone server serves those assets, copy the required public directory and generated static chunks explicitly or use a deliberately configured external asset service.

  • Can I change NEXT_PUBLIC values when starting the built container?

    Values inlined into the browser bundle are fixed at build time. Use a designed runtime configuration mechanism if one image must supply different public values after promotion.

  • Can I copy the same Dockerfile directly into a pnpm monorepo?

    Not without adapting lockfile installation, workspace context, output tracing and the standalone server path. This example assumes a single npm-managed application with a committed package-lock.json.

Sources

  1. Next.js standalone output — Next.js
  2. Containerizing a Next.js application — Docker
  3. Docker multi-stage builds — Docker
  4. Next.js environment variable timing — Next.js
  5. npm clean lockfile installation — npm

Comments

No comments yet. Be the first to join the conversation.

Leave a comment

Every comment is read by a moderator before it appears. Your email address is never published; it is used only to contact you about this comment.

Never published. Used only to contact you about this comment.

Loading the security check…

Comments are moderated before publication.