
Next.js-Docker-Images verstehen
Jonas ScholzEin Next.js-Dockerfile sieht oft aus wie mehrere Dockerfiles übereinander. Das hat einen Grund: Beim Installieren der Pakete, beim Build und beim Serverstart brauchst du jeweils andere Dateien. Schauen wir uns die Stufen einzeln an.
Vier Teile, ein fertiges Image
Das ursprüngliche Beispiel teilt das Dockerfile in vier Abschnitte auf: Basis-Image, Abhängigkeiten, Build und Runtime.

Die Grafik zeigt noch Node 18. Das vollständige Dockerfile weiter unten nutzt Node 24 und npm. Kopier den Code aus dem Text, damit du mit diesem Beispiel arbeitest.
Aktiviere zuerst die Standalone-Ausgabe in deiner Next.js-Config:
// next.config.mjs
const nextConfig = {
output: "standalone",
};
export default nextConfig;
Lass die anderen Einstellungen deines Projekts stehen. Next.js sammelt die Pakete, die dein Server braucht, unter .next/standalone. Die dort erzeugte server.js starten wir später direkt.
FROM node:24-bookworm-slim AS base
WORKDIR /app
FROM base AS dependencies
COPY package.json package-lock.json ./
RUN npm ci
FROM base AS build
COPY --from=dependencies /app/node_modules ./node_modules
COPY . .
RUN mkdir -p public && npm run build
FROM base AS runtime
ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME=0.0.0.0
COPY --from=build --chown=node:node /app/public ./public
COPY --from=build --chown=node:node /app/.next/standalone ./
COPY --from=build --chown=node:node /app/.next/static ./.next/static
USER node
EXPOSE 3000
CMD ["node", "server.js"]
Du brauchst ein npm-Projekt mit einer package-lock.json im Repository. Das build-Skript muss next build aufrufen. Leg vor dem Build eine .dockerignore an:
node_modules
.next
.git
.env
.env.*
npm-debug.log
Das Basis-Image
Alle Stufen nutzen dasselbe Node-Image. Ich nehme Debian slim, damit native Pakete beim Wechsel zwischen den Stufen dieselbe libc vorfinden. Alpine kann auch funktionieren. Test dann deine nativen Pakete, bevor du zusätzliche Kompatibilitätspakete installierst.
Ein Tag mit einer Node-Hauptversion kann später auf ein neueres Image zeigen. Bau regelmäßig neu, damit du Updates bekommst. Brauchst du für einen Release genau dasselbe Basis-Image, pinne es über einen Digest.
Abhängigkeiten installieren
Die Dependency-Stufe kopiert package.json und die Lockdatei. Danach installiert npm ci die Pakete. Erst später kommt der App-Code dazu. Änderst du nur eine Seite, kann Docker die installierten Pakete aus dem Cache nutzen.
Das ursprüngliche Next.js-Beispiel erkennt npm, Yarn und pnpm an ihren Lockdateien. Du brauchst nur den Teil für deinen Paketmanager. Nutze lokal und beim Docker-Build denselben Paketmanager und dieselbe Lockdatei.
Die App bauen
Die Build-Stufe übernimmt node_modules, kopiert den Code und startet npm run build. mkdir -p public legt bei Bedarf das Public-Verzeichnis an. So klappt das spätere COPY auch, wenn deine App keine eigenen Dateien unter public hat.
Build-Variablen müssen jetzt verfügbar sein. NEXT_PUBLIC_-Werte landen im Client-Bundle. Änderst du sie erst beim Containerstart, bleibt der alte Wert im Bundle. Zugangsdaten gehören deshalb weder in öffentliche Variablen noch in den Client-Code.
Die App starten
Die letzte Stufe kopiert den Standalone-Server, .next/static und public. Next.js nimmt die beiden statischen Verzeichnisse nicht automatisch in die Standalone-Ausgabe auf. Fehlen sie im Image, fehlen deiner Seite womöglich JavaScript, Styles oder Bilder.
Mit COPY --chown=node:node gehören die Dateien dem Benutzer node. Er kann damit auch die Runtime-Caches im .next-Verzeichnis schreiben. USER node startet den Server ohne Root-Rechte.
HOSTNAME=0.0.0.0 lässt den Server Anfragen über das Container-Netzwerk annehmen. Lauscht er nur auf localhost, erreichen ihn diese Anfragen nicht. PORT=3000 setzt den Port. EXPOSE beschreibt ihn nur.
Der letzte Befehl startet server.js direkt. Dafür brauchst du weder next start noch einen weiteren Build.
Ergebnis testen
docker build -t next-app .
docker run --rm --name next-app -p 3000:3000 next-app
Öffne http://localhost:3000. Test eine Seite, eine statische Datei und eine Serverroute, falls deine App eine hat. Die HTML-Seite kann funktionieren, obwohl Bilder oder andere statische Dateien fehlen.
Für komplexere Setups hilft das offizielle Next.js-Docker-Beispiel. Monorepos brauchen manchmal zusätzliche Einstellungen für Output Tracing oder einen anderen Serverpfad. Läuft das Image lokal, kannst du das Repository auf Sliplane deployen. Setz dafür den HTTP-Port auf 3000.