Skip to content
Move custom links under /@handle
Sniptide

Next.js Dockerfile

This Dockerfile builds a Next.js app in three stages and ships only the standalone server, which keeps the image small and runs it as a non-root user. Set output to standalone in next.config.ts, save both files below in your project root, and run docker build -t my-app . to build it.

Updated

Files

DockerfileRaw
# syntax=docker/dockerfile:1
# Needs output: "standalone" in next.config.ts.

FROM node:24-alpine AS base
WORKDIR /app
RUN corepack enable

# Install dependencies only when the lockfile changes.
FROM base AS deps
COPY package.json pnpm-lock.yaml ./
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
    pnpm install --frozen-lockfile

# Build the app.
FROM base AS build
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN pnpm build

# Run the standalone server as a non-root user.
FROM node:24-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production \
    NEXT_TELEMETRY_DISABLED=1 \
    PORT=3000 \
    HOSTNAME=0.0.0.0
RUN addgroup -S -g 1001 nodejs && adduser -S -u 1001 -G nodejs nextjs
COPY --from=build --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=build --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=build --chown=nextjs:nodejs /app/public ./public
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
.dockerignoreRaw
.git
.next
node_modules
npm-debug.log*
.env
.env*.local
Dockerfile
.dockerignore
coverage

Save with your agent

Connect the Sniptide MCP server once, then ask your agent to keep this template in your library. It reads the files from the JSON version of this page and saves them as a private snippet you can edit and file into a collection.

Terminal
claude mcp add --transport http sniptide https://app.sniptide.com/api/mcp
Prompt
Read https://sniptide.com/templates/nextjs-dockerfile.json and save its files as a private Sniptide snippet titled "Next.js Dockerfile".

Turn on standalone output

The Dockerfile copies .next/standalone, which Next.js only writes when you ask for it:

next.config.ts
import type { NextConfig } from "next"

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

export default nextConfig

The standalone folder holds a server.js and only the parts of node_modules the server needs, so the final image doesn't carry your dev dependencies or the build cache.

Terminal
docker build -t my-app .
docker run --rm -p 3000:3000 --env-file .env my-app

What each stage does

Stage
What it does
When to change it
base
Node 24 on Alpine with Corepack, which provides the pnpm version from packageManager in package.json.
When you move to a new Node version.
deps
Installs dependencies from the lockfile alone, so Docker reuses this layer until the lockfile changes. The cache mount keeps pnpm's store between builds.
When you switch package managers, as in the variant below.
build
Copies the source and runs pnpm build.
When the build needs variables, as described below.
runner
A fresh image with only the standalone server, the static files and public. It runs as the nextjs user.
Rarely.

HOSTNAME=0.0.0.0 makes the server listen on every interface inside the container. Without it, the server binds to the container's own host name and docker run -p can't reach it.

The .dockerignore file keeps node_modules, .next and your .env files out of the build context. That makes builds faster and stops secrets from ending up in an image layer.

Variables at build time and run time

Server-side variables such as DATABASE_URL are read when the server runs, so pass them with docker run --env-file or your platform's settings.

Variables that start with NEXT_PUBLIC_ are written into the JavaScript bundle during pnpm build. They must exist in the build stage:

Dockerfile
FROM base AS build
ARG NEXT_PUBLIC_APP_URL
ENV NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build

Then build with docker build --build-arg NEXT_PUBLIC_APP_URL=https://example.com -t my-app .. Only public values belong in a build argument, because anyone can read them from the image.

Variant: npm

Replace the base and deps stages, and run npm run build in the build stage.

Dockerfile
FROM node:24-alpine AS base
WORKDIR /app

FROM base AS deps
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci

Variant: Bun

Bun can install and build, while the standalone server still runs on Node.

Dockerfile
FROM oven/bun:1 AS deps
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile

FROM oven/bun:1 AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN bun run build

Keep the runner stage from the template as it is. To run the app with Docker Compose next to a database, use the Docker Compose Postgres template.

All templates
  • Docker and deployDocker Compose Postgres

    A Docker Compose file for Postgres with a persistent volume, a health check and init scripts, plus variants with an app service and pgAdmin.

  • Caddy
    Docker and deployCaddyfile reverse proxy

    A Caddyfile that reverse proxies a domain to your app with automatic HTTPS, compression and security headers, plus Docker, path and load balancing variants.

  • Project setupGitHub Actions Node CI

    A GitHub Actions workflow for Node.js that lints, type checks, tests and builds on every push and pull request, with pnpm, matrix and Postgres variants.

Pick where your library lives.

Use sniptide.com

Save your first pattern in the app, then connect Claude Code, Cursor or Codex with one sign-in in the browser.

Run it on your own server

One image and a database give you the app, the API and the MCP endpoint. Sniptide is open source under AGPL-3.0.

$docker compose up -d