Next.js auf Coolify deployen: Der Produktions-Setup-Guide

#Next.js auf Coolify deployen
Sandor Farkas - Founder & Lead Developer at Wolf-Tech

Sandor Farkas

Gründer & Lead Developer

Experte für Softwareentwicklung und Legacy-Code-Optimierung

Immer mehr Teams ziehen von Vercel auf eigene Server um, und Coolify ist einer der häufigsten Landeplätze geworden. Wenn du Next.js auf Coolify deployen willst und es sich wie eine Produktionsplattform verhalten soll statt wie ein Hobby-Setup, gibt es rund ein Dutzend Entscheidungen, die zählen: wie die App gebaut wird, welche Umgebungsvariablen zur Build-Zeit versus zur Laufzeit existieren, wie Deploys Downtime vermeiden und was bricht, wenn Features, die Vercel für dich erledigt hat, plötzlich dein Problem werden.

Dieser Guide geht das komplette Setup für eine Next.js-15-Anwendung auf Coolify durch, basierend auf dem, was wir für Kundenprojekte konfigurieren. Er setzt voraus, dass du bereits eine Coolify-Instanz auf einem VPS laufen hast. Wenn du dich noch zwischen Plattformen entscheidest, behandelt unser Vergleich von Coolify, Dokploy und Kamal diese Entscheidung im Detail.

Warum Next.js auf Coolify deployen

Die Kurzfassung: Kosten und Kontrolle. Ein Hetzner- oder OVH-Server für 20 bis 40 Euro im Monat kann denselben Traffic bedienen, der auf einer Serverless-Plattform jenseits des Free Tiers mehrere hundert Dollar kostet, und deine Daten bleiben auf Infrastruktur, die du wählst. Für europäische Teams mit DSGVO-Pflichten vereinfacht ein EU-Server unter eigener Kontrolle das Gespräch mit der Datenschutzbeauftragten erheblich.

Der Tausch: Du übernimmst Verantwortlichkeiten, die die Managed-Plattform bisher versteckt hat. Der Großteil dieses Guides handelt genau von diesen Verantwortlichkeiten.

Build-Setup: Nixpacks oder ein Dockerfile

Coolify bietet Nixpacks als Standard-Buildpack an. Es erkennt eine Next.js-App und produziert ohne Konfiguration ein funktionierendes Image, was für einen ersten Deploy in Ordnung ist. Für Produktion empfehlen wir trotzdem den Wechsel auf ein Dockerfile, aus einem Grund: den Standalone-Output-Modus.

In next.config.ts setzt du:

const nextConfig = {
  output: 'standalone',
};

Der Standalone-Modus lässt next build ein server.js plus nur die node_modules-Dateien ausgeben, die der Server tatsächlich importiert. Das resultierende Image schrumpft von 1 GB oder mehr auf grob 150 bis 250 MB. Kleinere Images bedeuten schnellere Deploys, schnellere Rollbacks und weniger Disk-Druck auf deinem VPS, was mehr zählt, als du erwarten würdest, sobald sich mehrere Apps einen Server teilen.

Ein minimales Produktions-Dockerfile sieht so aus:

FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]

Zeige in den Build-Einstellungen der Anwendung auf das Dockerfile und setze den Port auf 3000. Hat dein Server 4 GB RAM oder weniger, begrenze zusätzlich den Build-Speicher: NODE_OPTIONS=--max-old-space-size=2048 als Build-Time-Variable verhindert, dass next build die ganze Maschine mitreißt, während ein Deploy neben Live-Traffic läuft.

Umgebungsvariablen: Build-Zeit versus Laufzeit

Das ist die häufigste Ursache kaputter erster Deploys, sie verdient also eine präzise Erklärung.

Next.js teilt Umgebungsvariablen in zwei Gruppen. Alles mit Präfix NEXT_PUBLIC_ wird während next build ins JavaScript-Bundle eingebacken. Alles andere wird zur Laufzeit aus der Prozessumgebung gelesen. Coolify wiederum lässt dich jede Variable als verfügbar zur Build-Zeit, zur Laufzeit oder für beides markieren.

Die Konsequenzen sind konkret. Eine NEXT_PUBLIC_API_URL, die nur als Runtime-Variable gesetzt ist, wird im Browser undefined sein, weil der Build, der das Bundle gebacken hat, sie nie gesehen hat. Eine DATABASE_URL, die nur als Build-Time markiert ist, lässt den Container beim Start abstürzen. Die Merkregel: NEXT_PUBLIC_-Variablen müssen für die Build-Zeit aktiviert sein, Secrets sollten nur Runtime sein, damit sie nie in einem Image-Layer landen, und wenn du einen NEXT_PUBLIC_-Wert änderst, musst du einen Rebuild auslösen, keinen Restart, damit er wirkt.

Coolify speichert Secrets verschlüsselt und injiziert sie in den Container, für ein Single-Server-Setup brauchst du also keinen externen Secrets-Manager. Du brauchst Disziplin dabei, welche Checkbox jede Variable bekommt.

Domains und TLS

Coolify stellt Traefik vor deine Container und handhabt Zertifikate über Let's Encrypt. Setze in den Anwendungseinstellungen die FQDN auf https://yourdomain.com, richte einen A-Record auf die Server-IP, und das Zertifikat wird beim ersten Request automatisch ausgestellt und erneuert.

Zwei praktische Hinweise. Erstens: Setze den www-Redirect auf Traefik-Ebene in Coolify statt in Next.js-Middleware; im Proxy gehandhabt bleibt Redirect-Logik aus deinem Anwendungscode heraus. Zweitens: Wenn du ein CDN wie Cloudflare davor nutzt, fahre es im DNS-only-Modus, bis das erste Zertifikat ausgestellt ist, sonst scheitert die ACME-Challenge und du jagst einen verwirrenden Fehler.

Health Checks und Zero-Downtime-Deploys

Out of the box stoppt ein Coolify-Redeploy den alten Container und startet den neuen, was eine sichtbare Lücke von einigen Sekunden bedeutet. Für rollende Deploys muss der neue Container beweisen, dass er bereit ist, bevor der alte verschwindet, und das erfordert einen Health-Check-Endpoint.

Füge eine Route unter app/api/health/route.ts hinzu:

export async function GET() {
  return Response.json({ status: 'ok' });
}

Aktiviere dann den Health Check in Coolify mit Pfad /api/health, Port 3000 und einer Startperiode von 20 bis 30 Sekunden, damit der Check keinen Container killt, der noch bootet. Mit grünem Check startet Coolify den Ersatz, wartet, bis er besteht, schaltet den Traffic in Traefik um und stoppt erst dann den alten Container. Deploys werden für Nutzer unsichtbar.

Widersteh dem Drang, den Health-Endpoint die Datenbank prüfen zu lassen. Wenn deine Datenbank einen Schluckauf hat, willst du die App oben und gecachte Seiten mit Fehlerzuständen ausliefernd, nicht Traefik, das im selben Moment jeden Container aus der Rotation nimmt.

Postgres: Coolify-verwaltet oder extern

Coolify kann mit ein paar Klicks einen PostgreSQL-Container auf demselben Server provisionieren, mit geplanten Backups auf S3-kompatiblen Storage. Für Staging-Umgebungen und Produkte in der Frühphase ist das völlig vernünftig, und die Latenz zwischen App und Datenbank ist praktisch null, da sie sich einen Host teilen.

Die ehrliche Einschränkung ist operativ. Ein Datenbank-Container auf demselben VPS teilt dessen Schicksal: Eine volle Platte, eine Kernel Panic oder eine verpatzte Server-Migration reißt beide runter. Sobald echte Kunden von den Daten abhängen, ziehen wir Kunden meist auf ein Managed Postgres wie Neon, Supabase oder eine Hetzner Managed Database um und behalten Coolify für die Anwendungsschicht. Setze DATABASE_URL als Runtime-Variable, aktiviere Connection Pooling auf Provider-Seite, und der Anwendung ist es egal, wo die Datenbank lebt.

Welche Option du auch wählst: Teste einen Restore, bevor du einen brauchst. Ein Backup, das du nie wiederhergestellt hast, ist eine Hoffnung, kein Backup.

Automatische Deploys von GitHub

Verbinde das Repository über eine GitHub App unter Sources in Coolify statt über einen einfachen Deploy Key. Die App-Integration gibt dir Push-getriggerte Deploys, Commit-Status-Updates zurück auf GitHub und Preview-Deployments für Pull Requests, wenn du sie pro Anwendung aktivierst.

Der Ablauf nach dem Setup: Push auf deinen Produktions-Branch, Coolify baut das Image auf dem Server, Health Checks sichern die Umschaltung ab, und der Deploy erscheint als Status am Commit. Für Teams, die von Vercel kommen, stellt das den Großteil des gewohnten Workflows wieder her, minus das Per-Seat-Pricing.

Die Fallstricke, die niemand erwähnt, bis Produktion läuft

Diese drei Themen erklären die meisten überraschten Nachrichten, die wir von Teams ein paar Wochen nach der Migration bekommen.

ISR-Cache-Persistenz. Incremental Static Regeneration schreibt seinen Cache nach .next/cache im Container-Dateisystem. Jeder Deploy ersetzt den Container, der ISR-Cache startet also leer, und jede revalidierte Seite wird beim ersten Hit neu gebaut. Bei einer Handvoll Seiten merkt es niemand; bei zehntausend Produktseiten hämmern die ersten Minuten nach einem Deploy auf deine Datenquellen ein. Mounte ein Coolify Persistent Volume auf .next/cache, oder implementiere für Multi-Container-Setups einen eigenen Cache-Handler auf Redis-Basis. Unser Beitrag zu Caching- und Revalidation-Patterns in Next.js 15 geht tiefer darauf ein, wie die Cache-Schichten zusammenspielen.

Server-Sent Events und Sticky Sessions. Wenn du auf mehr als eine Container-Replica skalierst und SSE für Streaming-Responses nutzt, können der Stream und die Folge-Requests auf unterschiedlichen Replicas landen. Jeder In-Memory-Zustand des Streams bricht. Aktiviere entweder Sticky Sessions in der Traefik-Konfiguration oder, besser, halte Stream-Zustand in Redis, damit jede Replica jeden Request bedienen kann.

Speicher der Bildoptimierung. Die next/image-Optimierung läuft über Sharp in deinem Container, und Sharp allokiert aggressiv. Auf einem 2-GB-VPS kann ein Crawler, der ein paar Dutzend ungecachte Bildgrößen auf einmal anfragt, den Container in ein OOM treiben, der dann echten Traffic verliert, während er neu startet. Setze ein Memory-Limit auf den Container, damit der Kernel Sharp-Spitzen früh killt, beschränke images.remotePatterns und die erlaubten Device Sizes in next.config.ts, und wenn Bilder zentral für dein Produkt sind, verlagere die Optimierung in einen dedizierten Service oder einen Optimizer auf CDN-Ebene.

Wenn das Setup der einfache Teil ist

Eine Next.js-App auf Coolify zu bringen dauert einen Nachmittag. Sie ein Jahr lang gut zu betreiben umfasst die weniger glamouröse Arbeit: Monitoring, Backup-Restore-Übungen, Kapazitätsentscheidungen und das Wissen, welche Plattformgrenzen man umschifft, bevor sie dich nachts anrufen. Dieses operative Urteilsvermögen entscheidet, ob Self-Hosting-Migrationen gelingen oder scheitern.

Wenn du einen Umzug weg von Vercel planst, oder dein Self-Hosted-Setup schon existiert und sich wackliger anfühlt, als es sollte, helfen wir Teams genau damit als Teil unserer Arbeit in individueller Softwareentwicklung und Tech-Stack-Strategie. Schreib an hello@wolf-tech.io oder finde uns auf wolf-tech.io, dann schauen wir uns dein Setup gemeinsam an.