This guide takes a Next.js app from source code to a production Docker container. You’ll enable standalone output, build a small multi-stage image on Node.js 24, and run it on port 3000. The example uses npm and a package-lock.json; use the matching install command if your project uses another package manager. To build this image on each push and trigger a Serversinc deployment, follow the GitHub Actions Docker deployment guide.
Next.js supports Docker deployments with all framework features. Standalone output trims the runtime image to the files the server needs. You can run the image on any server that supports Docker, or follow the Next.js deploy guide for Serversinc to deploy from a GitHub branch.
Set up your Next.js app for standalone output.
Add output: 'standalone' to the Next.js configuration file in your project root. For a TypeScript config named next.config.ts, use:
import type { NextConfig } from 'next';
const nextConfig: NextConfig = { output: 'standalone',};
export default nextConfig;If your project uses next.config.js or next.config.mjs, keep that file’s existing module syntax and add the same output setting. Next.js creates a .next/standalone directory during the build. It contains a minimal server and the traced runtime dependencies.
This example pins Node.js 24.22.0, the current LTS release when this guide was updated. Check the Node.js release schedule before changing the base image. Keep the same Node version in the build and runtime stages.
Standalone output is for apps that need a Node.js server, including server-rendered routes and route handlers. A static export is a different deployment mode and doesn’t support features that need a server. Use this Dockerfile for the standalone server; see the Next.js output configuration if you’re deciding between standalone and export output.
Build a production Docker image.
Create a Dockerfile beside package.json and package-lock.json:
ARG NODE_VERSION=24.22.0-slim
FROM node:${NODE_VERSION} AS baseWORKDIR /app
FROM base AS dependenciesCOPY package.json package-lock.json ./RUN npm ci
FROM base AS builderCOPY --from=dependencies /app/node_modules ./node_modulesCOPY . .ENV NEXT_TELEMETRY_DISABLED=1RUN mkdir -p public && npm run build
FROM base AS runnerENV NODE_ENV=productionENV NEXT_TELEMETRY_DISABLED=1ENV PORT=3000ENV HOSTNAME="0.0.0.0"
RUN mkdir -p .next && chown node:node .nextCOPY --from=builder --chown=node:node /app/public ./publicCOPY --from=builder --chown=node:node /app/.next/standalone ./COPY --from=builder --chown=node:node /app/.next/static ./.next/static
USER nodeEXPOSE 3000CMD ["node", "server.js"]The dependency stage installs from the lockfile, so repeat builds use the same dependency versions. The builder compiles the app; the final stage copies only the standalone server, static assets, and public files. It runs as the unprivileged node user.
The mkdir -p public line handles projects that don’t have a public directory yet. If you use pnpm or Yarn, copy that package manager’s lockfile and workspace files into the dependencies stage, then replace npm ci and npm run build with its frozen-lockfile install and build commands. Don’t mix lockfiles.
For a monorepo, build from the Next.js app directory and copy the workspace manifests the package manager needs before installing. Next.js traces files from the app root by default. If the app imports files from another workspace package, configure outputFileTracingRoot to include the monorepo root and rebuild. Otherwise, the standalone output may omit files that exist on your machine but sit outside the app directory. See the output tracing notes before adding monorepo paths.
Next.js doesn’t copy public or .next/static into .next/standalone by default. The last two COPY instructions add those directories so the standalone server can serve your app’s public files and built assets. The HOSTNAME setting makes the server listen on the container interface, and PORT sets its internal port.
Create a .dockerignore file so local dependencies, build output, and environment files don’t enter the build context:
node_modules.next.gitout.env.env.*!.env.exampleKeep real secrets out of the image and repository. Supply runtime environment variables when you start the container or through your deployment platform. If your app needs environment values during next build, provide only the values required at build time; don’t bake private credentials into an image layer.
Values prefixed with NEXT_PUBLIC_ are inlined into browser JavaScript during next build. They are public, and changing them after the image is built won’t change the bundle. Keep database passwords and other private values unprefixed, and read them on the server at runtime. See the Next.js environment-variable guide for the rules and examples.
Build and run the container locally.
Build the image from the project root:
docker build -t nextjs-app .Start it and map your machine’s port 3000 to the container’s port 3000:
docker run --rm -p 3000:3000 nextjs-appOpen http://localhost:3000. The page should render from the standalone server. Stop it with Ctrl+C.
For a background run, add a name and detach the container:
docker run -d --name nextjs-app -p 3000:3000 nextjs-appCheck it with docker ps, then stop it with docker stop nextjs-app. For a real deployment, place the container behind a domain and HTTPS endpoint, and provide any runtime secrets through the platform rather than the Dockerfile.
If your deployment platform checks a health route before switching traffic, add a small route handler such as app/api/health/route.ts:
export function GET() { return Response.json({ status: 'ok' });}This gives the platform a fast response to check that the Next.js server started. Keep it shallow; don’t make the route depend on a database or an external API unless you want those failures to block a release. Your platform may use a different health-check path or configuration.
Push the image to a registry.
To run the same image on another server, tag it with the address of a registry that server can reach. Replace the example registry, owner, and release tag with your own values:
docker tag nextjs-app registry.example.com/OWNER/nextjs-app:release-1docker login registry.example.comdocker push registry.example.com/OWNER/nextjs-app:release-1docker login prompts for your registry credentials. Use a registry token with the minimum access it needs; don’t put the token in the Dockerfile or commit it to the repository. On the server, configure the app to use the pushed image, expose port 3000, and set its runtime environment variables. Keep the same image tag for the rollout so you can identify which build is running.
Check the image before deploying.
Verify the production build with the same image you intend to run:
- Build the image with
docker build -t nextjs-app .. - Start it with
docker run --rm -p 3000:3000 nextjs-app. - Open the app and exercise a server-rendered route, an API route if your app has one, and any image or static-asset paths it uses.
- Read the container output for startup errors. If a route fails only in the container, check that the build includes its files and that required runtime environment variables are set.
Standalone output supports the full Next.js feature set, but your app still needs its own production configuration. Review the official Next.js Docker deployment guidance and Docker’s Next.js container guide for details on caching, image optimization, and platform-specific settings.
If the page renders but assets return 404, check that both .next/static and public were copied into the final stage. If the container starts but the server can’t connect to a database or API, check that the runtime environment contains the expected variables and that the service is reachable from the container.
Deploy the image to a server.
The local run proves the image starts. To deploy it elsewhere, push it to a container registry your server can access, then configure the server to use that image and expose port 3000. Keep secrets in the runtime environment. For a GitHub-based workflow, deploy Next.js to your own server with Serversinc; it builds from your branch and handles the app’s domain and HTTPS.
If your app also needs PostgreSQL, use a persistent database and a separate runtime connection string. See the guide to automated PostgreSQL backups in Docker for backup and restore patterns.