Migrate to RunxBuild today!! Deploy More while Spending Less

Calculate your savings
unxBuild

Run Next.js in Production: next start, Standalone Output, Docker, and What a Process Manager Is Actually For

Sean

Platform Writer

Sep 21, 2026
9 min read

Running Next.js in production is next build followed by next start, with NODE_ENV=production, on a server that something restarts when it crashes. Everything else is about doing those three things well: building once in CI rather than on the server, using the standalone output so the deployable artefact is a small self-contained folder instead of the whole node_modules tree, getting environment variables in at the correct stage because some are baked at build time and some are read at runtime, and choosing a process manager or a container platform to keep the process alive and route traffic to it. The most common production mistake is running next dev behind a reverse proxy, and the second most common is rebuilding on every restart.

Run Next.js in Production: next start, Standalone Output, Docker, and What a Process Manager Is Actually For

There are two families of answer to this question. One is a managed platform that runs the build and the server for you, which is the right answer for most teams and needs no tutorial. The other is a machine or container you control, which is where the details below live. Both share the same three commands; only the supervision differs.

Table of contents

The three commands and the one to never run

npm ci                 # exact versions from the lockfile
npm run build          # next build: compiles pages, routes, server bundle
npm run start          # next start: serves the build on port 3000

next build produces the .next directory: the compiled server, the client bundles, the static HTML for prerendered pages, and the route manifest. next start runs a Node server that serves it. Both read NODE_ENV=production, which next start sets for you; do not override it. The build should happen once, in CI or a build step, and the same artefact should be started on every server and every restart. Building on the server at boot means every restart is a compile, and a compile that fails at three in the morning is a site that does not come back.

next dev is a development server with hot reloading, unminified bundles, and on-demand compilation. It is slower by an order of magnitude, leaks memory over long runs, and is not hardened. Running it in production, usually because it was the command that worked on the laptop, is the first thing to rule out when a Next.js site is inexplicably slow.

Standalone output: the artefact you actually deploy

By default the runtime needs the full node_modules directory next to .next, which for a typical app is hundreds of megabytes and thousands of packages the server never loads. The standalone output mode traces which files the server actually imports and copies only those into .next/standalone, along with a minimal server.js entry point.

// next.config.js
module.exports = {
  output: "standalone",
};
npm run build
# the deployable folder is .next/standalone, plus two things it does not include:
cp -r public .next/standalone/public
cp -r .next/static .next/standalone/.next/static

# run it
node .next/standalone/server.js

The public folder and the .next/static folder are excluded on purpose, because they are meant to be served by a CDN; copy them in if the Node server is serving everything. The result is a folder of a few tens of megabytes that starts with plain node, needs no npm install on the server, and is the right input for a Docker image.

Docker: the multi-stage image that stays small

FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM node:20-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:20-alpine AS run
WORKDIR /app
ENV NODE_ENV=production PORT=3000 HOSTNAME=0.0.0.0
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static
COPY --from=build /app/public ./public
USER node
EXPOSE 3000
CMD ["node", "server.js"]

Three details matter. HOSTNAME=0.0.0.0 makes the server listen on all interfaces instead of localhost, which is required inside a container. USER node drops root. And the final stage copies only the standalone output, so the image is small and contains no build tooling. A container platform then handles the supervision: it restarts the container on crash, routes traffic to the port, and keeps the previous image for rollback.

Environment variables: build time versus runtime

Next.js has two kinds of environment variable and confusing them produces a deploy that works in one environment and not another.

  • NEXT_PUBLIC_* variables are inlined into the client bundle at build time. Changing them after the build does nothing; you must rebuild. They are also visible to every visitor, so they must never hold a secret.
  • Everything else is read at runtime by the server. Database URLs, API keys, and anything used in server components, route handlers or getServerSideProps are read from process.env when the request is handled. These can differ between staging and production with the same build.

The practical rule: one build artefact, promoted through environments, with runtime variables set per environment on the platform. If a value has to be different per environment and it is NEXT_PUBLIC_, you either accept one build per environment or move the value to a runtime config endpoint the client fetches.

Keeping the process alive: PM2, systemd, or the platform

next start is a single Node process. If it crashes, nothing restarts it; if the machine reboots, nothing starts it. Something has to supervise it, and there are three reasonable choices on a machine you own.

  • PM2 is the common answer in tutorials. pm2 start npm --name web -- start, then pm2 save and pm2 startup so it survives reboots. Its cluster mode runs one process per CPU and reloads them one at a time, which gives zero-downtime restarts on a single box. It adds a dependency and a second thing to learn.
  • systemd is already there on any Linux server. A unit with Restart=always, Environment=NODE_ENV=production, and ExecStart=/usr/bin/node /srv/app/server.js does the same job for one process with no extra software. Logs go to the journal.
  • A container platform replaces both. The platform restarts the container, scales replicas, routes traffic, and shows the logs. This is the option that also removes the reverse proxy, the certificate renewal and the reboot handling from your list.

Whichever you choose, put a reverse proxy in front if the Node process is exposed directly: nginx or Caddy terminating TLS on 443 and forwarding to 3000. Node is fine at serving HTTP; it is not the right place to terminate certificates, buffer slow clients or serve the static folder at scale.

A production checklist

  1. Built once in CI with npm ci and next build; the server never runs npm install.
  2. output: standalone in the config; the deployed folder is .next/standalone plus static and public.
  3. next start or node server.js, never next dev.
  4. Runtime secrets in environment variables on the platform; NEXT_PUBLIC_ values understood to be public and baked.
  5. A supervisor: PM2, systemd, or the container platform’s restart policy.
  6. A reverse proxy or the platform’s router terminating TLS.
  7. The previous build kept, so a bad deploy is a rollback rather than a rebuild.
  8. Logs somewhere you can read them when the process is not on your laptop.

How this fits the rest of the stack

On a machine you administer, items five to eight on that checklist are yours to build and keep working. On a platform, they are the product. Next.js on RunxBuild runs the build from the repository, starts the server on a plan you choose, keeps each deploy for rollback, and puts the build log and the runtime log in the same place. The RunxBuild hosting calculator shows what the Next.js service costs beside the managed Postgres most apps end up adding, with autoscaling between two plans as the answer to the traffic question a single PM2 box cannot solve.

Useful related references:

FAQ

What is the correct command to run Next.js in production?

next build to compile, then next start to serve the build. next start sets NODE_ENV=production itself. Build once in CI and start the same artefact on every server; never run next dev in production.

What does output standalone do in Next.js?

It traces the files the server actually needs and copies them into .next/standalone with a minimal server.js, so the deployable folder is tens of megabytes and runs with plain node, without node_modules on the server. Copy public and .next/static in alongside it if the Node server serves them.

Do I need PM2 to run Next.js in production?

Not necessarily. You need something to restart the process on crash and start it on reboot. PM2 does that and adds cluster mode; systemd does it with no extra software; a container platform does it along with routing, TLS and rollback. Choose one.

Why does my environment variable not change after I update it?

If it starts with NEXT_PUBLIC_, it was inlined into the client bundle at build time and requires a rebuild to change. Server-side variables are read at runtime and take effect on restart. Never put a secret in a NEXT_PUBLIC_ variable; it is visible to every visitor.

Should Next.js serve traffic directly on port 3000?

Put a reverse proxy or a platform router in front. nginx or Caddy should terminate TLS on 443 and forward to 3000. Node is not the right place to terminate certificates, buffer slow clients or serve the static folder under load.

#run next js in production#next start production#next.js standalone output#next.js docker production#next.js pm2