Next.js-Docker-Images verstehen

Next.js-Docker-Images verstehen

Jonas Scholz - Co-Founder von sliplane.ioJonas Scholz
5 min

Ein 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 vier Abschnitte des ursprünglichen Next.js-Dockerfiles

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.

Dockerfile
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.

Willkommen auf deiner Cloud-Plattform

Sliplane macht es einfach, deine Apps in der Cloud zu deployen und zu skalieren. Starte mit einem Container oder deinem Lieblings-Framework und wachse von da aus. Probier es jetzt aus!