Next.js auf DigitalOcean deployen
Folge dem auf deinem eigenen Server. Kostenlos starten, keine Karte nötig
Du hast eine Next.js-App, die lokal funktioniert, und willst sie auf einem DigitalOcean-Droplet laufen lassen, den du selbst kontrollierst, statt auf einer Plattform, die nach Nutzung abrechnet. Diese Anleitung führt dich von einem leeren Droplet zu einer laufenden App auf deiner eigenen Domain: ein kleines Docker-Image, gebaut aus Next.js’ Standalone-Output, Caddy davor für HTTPS-Zertifikate und eine kurze Routine für den nächsten Release.
Was du brauchst
- Ein DigitalOcean-Droplet. Die Basic-Größe
s-1vcpu-2gb(1 vCPU, 2 GB Arbeitsspeicher, 50 GB SSD, 2.000 GB Transfer) kostet 12 $ im Monat und betreibt eine typische Next.js-App problemlos. Ist dein Build rechenintensiv, kostet die Größes-2vcpu-4gb(2 vCPU, 4 GB, 80 GB SSD) 24 $ im Monat. Preise stammen von DigitalOceans Droplet-Preisseite. - Eine Domain, deren DNS du bearbeiten kannst.
- Ein DigitalOcean-Account mit hinterlegtem SSH-Key.
- Eine Next.js-App in einem Git-Repository, mit committetem Lockfile (
package-lock.json,yarn.lockoderpnpm-lock.yaml).
Diese Anleitung nutzt Next.js 16.4.3 und Node.js 24, die aktuelle LTS-Release-Linie, über das offizielle node:24-slim-Image.
Schritt 1: Das Droplet erstellen
Wähle im DigitalOcean-Kontrollpanel Create → Droplets. Nimm Ubuntu 24.04 (LTS) x64, den Basic-Plan mit einer Regular-SSD, und die 12-$-Größe. Wähle eine Region nah an deinen Nutzern (Frankfurt oder Amsterdam halten die Daten in der EU), wähle deinen SSH-Key, und erstelle das Droplet.
Sobald es fertig ist, kopiere seine öffentliche IPv4-Adresse und verbinde dich:
ssh root@your-droplet-ip
Du solltest das Ubuntu-Willkommensbanner und einen root@-Prompt sehen.
Während du in den DNS-Einstellungen deiner Domain bist, lege einen A-Record an, der die Domain (oder eine Subdomain wie app.example.com) auf diese IP zeigen lässt. DNS kann ein paar Minuten zur Verbreitung brauchen, und Caddy benötigt es bereits vor Schritt 5, um ein Zertifikat zu bekommen, also lohnt es sich, das jetzt zu erledigen.
Schritt 2: Docker installieren
Ubuntu liefert Docker nicht mit. Docker’s offizielles Convenience-Skript installiert die Engine und das Compose-Plugin in einem Durchgang:
curl -fsSL https://get.docker.com | sh
Prüfe, dass beides vorhanden ist:
docker --version
docker compose version
Du solltest je eine Versionszeile sehen, ohne Fehler.
Schritt 3: Standalone-Output aktivieren
Next.js kann genau nachverfolgen, welche Dateien der Produktionsserver braucht, und sie nach .next/standalone kopieren, zusammen mit einer kleinen server.js, die die App startet. Das hält das finale Image klein, weil es nicht deinen gesamten node_modules-Ordner mitschleppt.
Öffne next.config.ts (oder next.config.js) und füge output: "standalone" zum Config-Objekt hinzu, mit allen Optionen, die du bereits hast:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "standalone",
};
export default nextConfig;
Committe die Änderung. Du solltest .next/standalone/server.js sehen, sobald du als Nächstes lokal npm run build ausführst.
Schritt 4: Ein Dockerfile hinzufügen
Füge dieses Dockerfile im Wurzelverzeichnis des Repositorys hinzu. Es folgt der Struktur des offiziellen with-docker-Beispiels im Next.js-Repository: Eine Stage installiert Abhängigkeiten mit dem Paketmanager, zu dem dein Lockfile gehört, eine baut die App, und die letzte führt nur den Standalone-Output als unprivilegierten node-User aus.
FROM node:24-slim AS dependencies
WORKDIR /app
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN if [ -f package-lock.json ]; then npm ci --no-audit --no-fund; \
elif [ -f yarn.lock ]; then corepack enable yarn && yarn install --frozen-lockfile --production=false; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm install --frozen-lockfile; \
else echo "No lockfile found." && exit 1; \
fi
FROM node:24-slim AS builder
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
COPY . .
ARG NEXT_PUBLIC_SITE_URL
ENV NEXT_PUBLIC_SITE_URL=$NEXT_PUBLIC_SITE_URL
ENV NODE_ENV=production
RUN if [ -f package-lock.json ]; then npm run build; \
elif [ -f yarn.lock ]; then corepack enable yarn && yarn build; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm build; \
fi
FROM node:24-slim AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
COPY --from=builder --chown=node:node /app/public ./public
RUN mkdir .next && chown node:node .next
COPY --from=builder --chown=node:node /app/.next/standalone ./
COPY --from=builder --chown=node:node /app/.next/static ./.next/static
USER node
EXPOSE 3000
CMD ["node", "server.js"]
Zwei Zeilen brauchen eine Erklärung. ARG NEXT_PUBLIC_SITE_URL und die ENV-Zeile danach existieren, weil Next.js jede NEXT_PUBLIC_-Variable während next build ins Browser-Bundle einbettet. Ein Wert, den du erst beim Containerstart bereitstellst, kommt dafür zu spät. Füge pro NEXT_PUBLIC_-Variable, die deine App nutzt, ein ARG/ENV-Paar hinzu, oder entferne das Paar, falls keine genutzt wird. Serverseitige Variablen (Datenbank-URLs, API-Secrets) gehören nicht hierhin; sie werden zur Laufzeit gelesen.
Füge daneben eine .dockerignore hinzu, damit dein lokales node_modules, Build-Output und Environment-Dateien nie in den Build gelangen:
.git
.next
node_modules
.env*
.env* auszuschließen ist wichtig: Ohne das würde COPY . . deine Secrets in eine Build-Layer kopieren.
Schritt 5: Compose und Caddy hinzufügen
Next.js’ eigene Self-Hosting-Anleitung empfiehlt, einen Reverse Proxy vor den Node-Server zu setzen, statt ihn direkt freizugeben. Caddy erledigt diesen Job und besorgt und erneuert auch selbstständig ein Let’s-Encrypt-Zertifikat.
Erstelle compose.yaml im Repository:
services:
app:
build:
context: .
args:
NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL}
restart: unless-stopped
env_file: .env
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
depends_on:
- app
volumes:
caddy-data:
caddy-config:
Nur Caddy gibt Ports nach außen frei. Die App lauscht auf Port 3000 innerhalb des Compose-Netzwerks, wo Caddy sie über ihren Service-Namen erreicht. Das caddy-data-Volume hält deine Zertifikate, sodass sie Neustarts überleben.
Dann ein Caddyfile daneben, mit deiner eigenen Domain:
app.example.com {
reverse_proxy app:3000
}
Committe beide Dateien.
Schritt 6: Code und Secrets auf das Droplet legen
Klone das Repository auf dem Droplet. Für ein privates Repository füge zuerst einen Read-only-Deploy-Key zum Repository hinzu, oder klone über HTTPS mit einem Token:
git clone https://github.com/you/your-app.git /opt/your-app
cd /opt/your-app
Erstelle dort die .env-Datei. Sie kommt nie in Git (die Standard-.gitignore von create-next-app ignoriert .env*-Dateien bereits), sie bleibt also zwischen Deploys auf dem Server:
cat > .env <<'EOF'
NEXT_PUBLIC_SITE_URL=https://app.example.com
DATABASE_URL=postgres://user:password@db-host:5432/app
EOF
chmod 600 .env
Compose nutzt diese eine Datei zweimal: Sie befüllt ${NEXT_PUBLIC_SITE_URL} in compose.yaml für den Build, und env_file gibt jede Variable an die laufende App weiter.
Schritt 7: Bauen und starten
docker compose up -d --build
Der erste Build dauert ein paar Minuten. Prüfe dann die Container:
docker compose ps
Du solltest app und caddy beide mit Status Up sehen. Verfolge Caddys Log, während es das Zertifikat besorgt:
docker compose logs -f caddy
Du solltest eine Zeile sehen, die bestätigt, dass das Zertifikat erfolgreich bezogen wurde. Öffne https://app.example.com und deine App lädt mit gültigem Zertifikat.
Schritt 8: Updates ausliefern
Jeder Release ist dieselben drei Befehle auf dem Droplet:
cd /opt/your-app
git pull
docker compose up -d --build app
Compose baut das Image neu und ersetzt den app-Container. Caddy läuft weiter und hält sein Zertifikat. Erwarte ein paar Sekunden Fehler, während der neue Container startet, da es nur eine Kopie der App gibt; falls das relevant ist, deploye zu einer ruhigen Zeit.
Zum Zurückrollen checke den vorherigen Commit aus und baue neu:
git log --oneline -5
git checkout <previous-commit>
docker compose up -d --build app
Führe git checkout main (oder deinen Branch-Namen) vor dem nächsten normalen Deploy aus.
Schritt 9: Backups
Die App selbst ist stateless: ihr Code liegt in Git und ihr Image kann jederzeit neu gebaut werden. Was du nicht neu bauen kannst, ist die .env-Datei, bewahre also eine Kopie irgendwo anders als auf dem Droplet auf, etwa in einem Passwort-Manager. Nutzt die App eine Datenbank, ist diese Datenbank das eigentlich zu sichernde Gut; DigitalOceans Droplet-Backups oder ein geplanter pg_dump, der vom Server kopiert wird, funktionieren beide.
Next.js hält seinen Page- und Datencache standardmäßig auf der Disk des Containers. Das funktioniert für eine einzelne Instanz wie diese hier, und der Cache startet nach jedem Deploy einfach leer, da der Container ersetzt wird.
Die Abkürzung: mit Railyard
Der manuelle Weg funktioniert, und du kennst jetzt jedes Einzelteil. Railyard erledigt denselben Job ohne das Dockerfile, die Compose-Datei oder das Caddyfile. Siehe Next.js auf DigitalOcean deployen für den vollständigen Durchgang.
- Öffne im DigitalOcean-Kontrollpanel API und generiere ein Personal Access Token mit Lese- und Schreibrechten.
- Gehe in Railyard zu Servers → Add a server → Create on your cloud, wähle DigitalOcean, eine Region und eine Größe (
s-1vcpu-2gbist der Standard). Das Droplet wird in deinem eigenen Account erstellt und DigitalOcean rechnet direkt mit dir ab, ohne Aufschlag. Du kannst auch ein bereits vorhandenes Droplet mitbringen: jeder Ubuntu-22.04- oder -24.04-Server mit passwortlosemsudound offenen Ports 22, 80 und 443. - Deploye das Repository, oder öffne
https://app.railyard.run/deploy?repo=https://github.com/you/your-app.gitmit deiner eigenen Repository-URL.
Railyard wählt npm, pnpm, yarn oder bun anhand deines Lockfiles, führt dein Build-Skript aus und startet die App mit npm start. Die App lauscht auf $PORT, und HOST und ähnliche Variablen werden für dich gesetzt. Füge Postgres oder Redis über die App-Seite hinzu, und der Connection-String wird als Variable injiziert. Große Frontend-Builds werden an den Arbeitsspeicher des Build-Servers angepasst, du musst also kein Swap oder Nodes Heap-Größe selbst verwalten. Jeder Push deployt neu, und HTTPS wird für dich übernommen.
Der Free-Plan deckt einen Server plus einen Datenbankserver und drei Apps ab, ohne Kreditkarte. Pro kostet 19 $ im Monat pro Team und Team kostet 49 $ im Monat pro Team. Die Node.js-Docs listen die Erkennungsregeln, und der Quickstart führt durch einen ersten Deploy.
Troubleshooting
bind: address already in use für Port 80 oder 443. Ein anderer Webserver auf dem Droplet belegt den Port schon. sudo ss -tlnp | grep -E ':(80|443) ' zeigt, welcher Prozess. Stoppe und deaktiviere ihn (sudo systemctl disable --now nginx, zum Beispiel), und führe dann docker compose up -d erneut aus.
Das Zertifikat kommt nie an. Fast immer DNS. Führe dig +short app.example.com aus und vergleiche die Antwort mit der IP des Droplets. Unterscheiden sie sich, korrigiere den A-Record und warte; Caddy versucht es selbstständig weiter. Hat das Droplet eine Cloud-Firewall, müssen Ports 80 und 443 offen sein, da Let’s Encrypt darüber validiert.
Ein NEXT_PUBLIC_-Wert ist leer oder veraltet im Browser. Er fehlte zur Build-Zeit. Prüfe, dass er in .env steht, dass compose.yaml ihn unter build.args weitergibt, dass das Dockerfile ein passendes ARG/ENV-Paar hat, und baue neu mit docker compose up -d --build app. Nur .env ändern und neu starten reicht dafür nicht.
next build wird mitten im Lauf abgebrochen. Dem 2-GB-Droplet ist der Speicher ausgegangen, und der Kernel hat den Build abgebrochen. Füge Swap hinzu und baue erneut:
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
Auf die 24-$-Größe zu skalieren funktioniert auch, ebenso das Image anderswo zu bauen und von einer Registry zu ziehen.
Login oder Redirects zeigen auf die falsche URL. Deine Auth-Library hat noch eine alte URL. Nutzt du Auth.js oder NextAuth, prüfe AUTH_URL oder NEXTAUTH_URL in .env, und starte dann mit docker compose up -d app neu.
FAQ
Funktioniert das mit pnpm oder yarn? Ja. Das Dockerfile wählt den Paketmanager anhand des Lockfiles, genau wie das offizielle Next.js-Beispiel. Ein bun-Lockfile wird von diesem Dockerfile nicht behandelt; füge einen Zweig dafür hinzu oder committe ein package-lock.json.
Kann ich mehr als eine App auf demselben Droplet laufen lassen? Ja. Gib jeder App ihren eigenen Service in compose.yaml und ihren eigenen Site-Block im Caddyfile (other.example.com { reverse_proxy other:3000 }). Ein Caddy bedient alle davon und verwaltet jedes Zertifikat.
Funktionieren Image Optimization und Proxy noch? Laut Next.js’ Self-Hosting-Guide funktionieren sowohl die next/image-Optimierung als auch Proxy (die Datei-Konvention, die früher Middleware hieß) ohne jede Konfiguration, wenn du den Next.js-Server betreibst, was genau das ist, was server.js tut.
Brauche ich eine Datenbank? Nur, falls deine App eine nutzt. Richte DATABASE_URL in .env auf eine Managed-Datenbank oder eine, die du selbst betreibst, und sichere sie nach ihrem eigenen Zeitplan.
Sollte ich Telemetrie deaktivieren? Next.js sammelt standardmäßig anonyme Telemetrie. Füge ENV NEXT_TELEMETRY_DISABLED=1 zur Builder- und Runner-Stage hinzu, wenn du lieber aussteigen möchtest.
Ist das einmal eingerichtet, ist ein Release ein git pull und ein Compose-Befehl, und die Rechnung ist eine flache 12 $ im Monat mit 2.000 GB enthaltenem Transfer, statt einer Zahl, die sich mit deinem Traffic bewegt.
Warum Leute sich dafür Railyard entscheiden
- Dein eigenes Cloud-Konto. Deploye auf DigitalOcean, Hetzner, Vultr, AWS, OVHcloud oder jedem Linux-Server per SSH, direkt von deinem Anbieter abgerechnet, ohne Aufschlag.
- Ein fester Preis pro Team. Free ist für immer kostenlos: ein Server plus ein Datenbankserver, drei Apps, keine Karte. Pro kostet 19 $ im Monat für bis zu fünf Server und unbegrenzte Apps. Team kostet 49 $ im Monat mit unbegrenzten Servern. Keine Kosten pro App, pro Deployment, pro Sitz oder pro Gigabyte.
- Templates mit einem Klick. Deploye Open-Source-Apps, wie Datenbanken, Analytics und Dev-Tools, mit einem Klick auf deinem eigenen Server.
- Verwaltete Datenbanken. Postgres, MySQL und Redis mit geplanten, wiederherstellungsgeprüften Backups und Point-in-Time-Recovery.
- Push-to-Deploy. Deploye von GitHub, GitLab oder der CLI, mit automatischem HTTPS und Releases ohne Ausfallzeit.