Ein praktisches Rahmenwerk für die Bereitstellung von Node.js-Apps in der Produktion
Einen umfassenden Produktions-Checkliste für Node.js-Anwendungen, der Server, Geheimnisse, Prozessverwaltung, HTTPS, CI/CD, Protokollierung und Backups umfasst.
Die Ausführung einer Node.js-Anwendung auf dem eigenen Rechner ist der einfachere Teil.
npm run dev
Ihre API antwortet. Ihre Datenbank verbindet sich. Alles scheint einwandfrei zu funktionieren.
Dann fragt jemand:
"Okay, wie deployen wir das in die Produktion?"
Dann wird klar, dass das Erstellen der API nur die halbe Arbeit war.
Der Übergang in die Produktion bringt eine ganz neue Reihe von Fragen mit sich:
- Wo wird die Anwendung tatsächlich ausgeführt?
- Was sorgt dafür, dass sie weiterläuft, falls sie abstürzt?
- Wie erreicht der eingehende Verkehr Ihren Node.js-Prozess?
- Wo sollten Umgebungsvariablen gespeichert werden?
- Wie richten Sie HTTPS ein?
- Wie bringen Sie neue Versionen heraus?
- Wie fangen Sie Fehler ab, sobald sie auftreten?
- Was passiert, wenn der Server selbst neu gestartet wird?
Hier ist ein praktisches Rahmenwerk zur Überlegung einer Node.js-Produktionsumgebung.
1. Die Produktionsarchitektur verstehen
Eine grundlegende Produktionsumgebung sieht in der Regel so aus:
Internet
│
▼
┌─────────┐
│ Nginx │
│ :80/443│
└────┬────┘
│
▼
┌─────────────┐
│ Node.js │
│ Application │
└──────┬──────┘
│
┌────────┼────────┐
▼ ▼ ▼
PostgreSQL Redis External APIs
Das grundlegende Prinzip ist, dass Endbenutzer im Allgemeinen nicht direkt mit etwas wie folgendem verbunden werden sollten:
localhost:3000
Stattdessen steht Nginx davor, nimmt öffentliche Anfragen entgegen und leitet sie an Ihre Node.js-Anwendung weiter.
2. Den Server vorbereiten
Sie können einen VPS oder eine Cloud-Virtuellmaschine bei Anbietern wie AWS, GCP, Azure oder DigitalOcean bereitstellen.
Zuerst müssen Sie die Maschine selbst einrichten.
Auf Ubuntu beginnt das in der Regel damit:
sudo apt update
sudo apt upgrade -y
Danach installieren Sie die Tools, von denen Ihre Anwendung abhängt.
Für Node.js selbst ist es im Allgemeinen eine gute Idee, es über einen Versionmanager wie nvm zu installieren, damit Sie genau kontrollieren können, welche Version läuft.
Überprüfen Sie die installierten Versionen mit:
node -v
npm -v
Die in der Produktion laufende Node-Version sollte mit der übereinstimmen, die Sie lokal testen.
Es mag sich um ein kleines Detail handeln, aber nicht übereinstimmende Versionen können nach dem Deploy frustrierende und schwer nachvollziehbare Fehler verursachen.
3. Legen Sie keine Geheimnisse in Ihren Code
Diesen Fehler ist leicht zu machen – und genauso leicht zu vermeiden.
Vermeiden Sie es, Zugangsdaten so hardzuzoden:
const DATABASE_URL =
"postgresql://user:password@database.com/mydb";
Und übertragen Sie niemals Geheimnisse in Ihre Git-Historie.
Definieren Sie sie stattdessen als Umgebungsvariablen:
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://...
REDIS_URL=redis://...
JWT_SECRET=...
Lesen Sie sie anschließend in Ihrem Code mit folgendem Verfahren ein:
process.env.DATABASE_URL
Stellen Sie sicher, dass Ihre .env-Dateien aus dem Versionsschutz ausgeschlossen sind:
.env
.env.production
Ein einzelnes offengelegtes Datenbankpasswort kann weitaus größeren Schaden anrichten als irgendein Deployment-Fehler.
4. Anwendung erstellen
Vor dem Starten der App sollten nur Produktiv-Abhängigkeiten installiert werden, und falls Ihr Framework es erfordert, muss ein Build-Schritt ausgeführt werden.
Ein TypeScript-Projekt könnte beispielsweise wie folgt ausgeführt werden:
npm ci
npm run build
Und anschließend wird die kompilierte Ausgabe gestartet mit:
npm start
Die genauen Befehle variieren je nach Ihrer Technologieauswahl.
Wichtig ist folgendes Prinzip:
Produktivverkehr sollte immer auf die Produktivversion der Anwendung zugreifen, niemals auf den Entwicklungsserver.
Anders ausgedrückt: Lassen Sie auf keinen Fall versehentlich etwas wie folgendes als laufenden Prozess zurück:
npm run dev
als Ihren Live-Prozess.
5. Was passiert, wenn Node.js abstürzt?
Nehmen wir an, Sie starten Ihre App direkt so:
node dist/server.js
Irgendwann geht etwas schief und der Prozess stirbt unerwartet ab.
Ihre API ist nun offline, und es gibt nichts, was sie wieder zum Laufen bringen könnte.
Genau dieses Problem löst ein Prozessmanager.
PM2 ist eine weit verbreitete Wahl für diese Aufgabe.
Installieren Sie es global:
npm install -g pm2
Führen Sie anschließend Ihre Anwendung unter der Überwachung von PM2 aus:
pm2 start dist/server.js --name my-api
Überprüfen Sie ihren Status:
pm2 status
Ausgeben Sie die Protokolle:
pm2 logs my-api
Oder starten Sie sie auf Wunsch neu:
pm2 restart my-api
Es geht nicht nur um die Benutzerfreundlichkeit. Vielmehr wird Ihr Prozess nun aktiv überwacht, anstatt in einem Terminalfenster zu laufen und darauf zu hoffen, dass nichts ihn beendet.
6. Stellen Sie sicher, dass die Anwendung nach einem Serverneustart startet
Server werden gelegentlich neu gestartet, egal ob absichtlich oder nicht.
Zum Beispiel:
Server reboot
↓
Operating system starts
↓
Node.js application?
Man möchte nicht jedes Mal manuell per SSH einloggen müssen, wenn das passiert.
PM2 kann einen Startskript für Ihr System erstellen:
pm2 startup
Dann speichern Sie die derzeit laufende Prozessliste:
pm2 save
Sobald das eingerichtet ist, kann Ihre Anwendung nach einem Neustart automatisch wieder online gehen.
7. Nginx vor Node.js platzieren
Nehmen wir an, Ihr Node.js-Server ist auf folgendes gebunden:
localhost:3000
Aber Ihre Benutzer rufen folgendes auf:
https://api.example.com
Nginx kann diese Lücke schließen, indem es als Reverse-Proxy fungiert.
Eine vereinfachte Konfiguration könnte so aussehen:
server {
listen 80;server_name api.example.com;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Dadurch wird der Anfragenpfad wie folgt:
User
↓
https://api.example.com
↓
Nginx :80
↓
Node.js :3000
Auf diese Weise muss die Portnummer, auf der Ihre Node.js-Anwendung lauscht, niemals direkt im öffentlichen Internet sichtbar sein.
8. HTTPS hinzufügen
Die Ausführung Ihrer Produktions-API über reines
http://
Das reicht nicht aus. Es muss über einen bestimmten Kanal bereitgestellt werden.
https://
Ein weit verbreiteter Ansatz ist die Kombination von Let's Encrypt mit Certbot.
Fangen Sie damit an, Certbot zu installieren:
sudo apt install certbot python3-certbot-nginx
Dann beantragen und aktivieren Sie ein Zertifikat für Ihre Domain:
sudo certbot --nginx -d api.example.com
Certbot kümmert sich um die Bereitstellung des Zertifikats sowie die Einrichtung von HTTPS für Sie.
Sobald das erledigt ist, sieht der Anfrageablauf so aus:
Client
↓
HTTPS
↓
Nginx
↓
Node.js
↓
Database / Redis
9. Vergessen Sie Ihre Firewall nicht
Nicht jeder Port auf Ihrem Server muss von der Außenwelt erreichbar sein.
In der Regel möchten Sie, dass die Öffentlichkeit folgende Ports erreichen kann:
80 → HTTP
443 → HTTPS
zusammen mit dem SSH-Zugang auf:
22
Ports, die von PostgreSQL oder Redis verwendet werden, sollten in der Regel nicht für das Internet zugänglich sein, es sei denn, Sie haben einen spezifischen Grund und solide Zugriffskontrollen eingebaut.
Die genauen Regeln variieren je nach Ihrer Konfiguration, doch die Leitregel bleibt dieselbe:
Exponieren Sie nur das, was tatsächlich exponiert werden muss.
10. Manuelle Bereitstellung funktioniert – bis sie es nicht mehr tut
Zu Beginn besteht eine Bereitstellung möglicherweise nur aus:
git pull
npm install
npm run build
pm2 restart my-api
Das ist für ein kleines Projekt völlig in Ordnung.
Aber früher oder später werden Sie feststellen, dass Sie immer wieder denselben Ablauf durchlaufen:
Developer pushes code
↓
SSH into server
↓
git pull
↓
install dependencies
↓
build
↓
restart
Ab diesem Punkt lohnt es sich, die Prozesse zu automatisieren.
11. CI/CD verändert den Workflow
Mit Tools wie GitHub Actions kann der Prozess wie folgt aussehen:
Developer
↓
git push
↓
GitHub
↓
CI/CD Pipeline
↓
Build + Test
↓
Deploy
↓
Production
Eine minimale Workflow-Definition könnte so aussehen:
name: Deploy
on:
push:
branches:
- mainjobs:
deploy:
runs-on: ubuntu-latest steps:
- uses: actions/checkout@v4 - name: Install dependencies
run: npm ci - name: Build
run: npm run build - name: Test
run: npm test
Die konkreten Bereitstellungs Schritte hängen von Ihrer eigenen Infrastruktur ab.
Mehr wichtig ist das zugrundeliegende Prinzip:
Automatisieren Sie keinen Bereitstellungsprozess, den Sie noch nicht verstehen.
Lernen Sie zunächst, wie man es manuell durchführt.
Nur danach können Sie die wiederholten Schritte automatisieren.
12. Logging ist nicht optional
Eine Anwendung kann scheinbar einwandfrei laufen, obwohl echte Benutzer auf Fehler stoßen.
Durch Logs können Sie das herausfinden.
Zumindest benötigen Sie Antworten auf folgende Fragen:
When did the error happen?
Which endpoint failed?
What status code was returned?
What was the error?
How long did the request take?
Ein einfaches Beispiel:
console.error({
message: error.message,
endpoint: req.originalUrl,
method: req.method,
timestamp: new Date().toISOString()
});
Bei Anwendungen in großem Maßstab bringen strukturierte Logs in Kombination mit zentralisierter Logaggregation weitaus bessere Ergebnisse als das Verwenden von console.log()-Aufrufen überall im Code.
13. Überwachen Sie mehr als nur Fehler
Das Fehlen von Ausnahmen bedeutet nicht, dass Ihr System gesund ist.
Auch Informationen zu folgenden Aspekten sind wichtig:
CPU
Memory
Disk
Request latency
Error rate
Database performance
Redis health
Traffic
Zum Beispiel:
Requests → 1,500/min
Average latency → 180ms
Error rate → 0.4%
CPU → 42%
Memory → 61%
Messwerte wie diese liefern ein weitaus vollständigeres Bild davon, wie Ihr System tatsächlich performt.
14. Backups sind wichtiger als die Bereitstellung
Hier ist eine Situation, über die die meisten Entwickler lieber nicht nachdenken möchten:
Production database
↓
Something goes wrong
↓
Data disappears
Ihren Anwendungscode können Sie immer erneut bereitstellen.
Ihre Datenbank kann jedoch Daten enthalten, die einmal verloren gegangen sind, einfach nicht wieder erzeugt werden können.
Deshalb sind Backups ebenfalls keine Option.
Für alles Wichtige in der Produktion benötigen Sie einen Backup-Plan – und genauso wichtig ist es, tatsächlich zu wissen, wie man aus diesen Backups wiederherstellt.
Ein Backup, dessen Wiederherstellung Sie noch nie ausprobiert haben, ist kein Backup, dem man vertrauen sollte.
15. Bereitstellungen ohne Ausfallzeiten sind ein separates Problem
Irgendwann wird es unzumutbar, Ihre Anwendung vorübergehend offline zu nehmen, um sie neu zu bereitstellen.
Betrachten Sie dieses Szenario:
Old version running
↓
New version deployed
↓
Traffic gradually moves
↓
Old version removed
Je nach Aufbau Ihrer Infrastruktur können Sie folgende Ansätze wählen:
- Den Ausführung mehrerer Kopien Ihres Node.js-Prozesses gleichzeitig
- Den integrierten Cluster-Modus von PM2
- Einen Load Balancer, der den Verkehr auf verschiedene Instanzen verteilt
- Die Bereitstellung von Updates schrittweise, Node für Node, anstatt alle auf einmal
- Den Wechsel des Verkehrs zwischen einer alten und einer neuen Umgebung (Blue-Green-Ansatz)
- Das Verpacken der Anwendung in Container
- Die Orchestrierung aller Komponenten mit Kubernetes
Aber ein Node.js-API bedeutet nicht automatisch, dass Sie Kubernetes benötigen.
Bleiben Sie anfangs einfach und erweitern Sie die Architektur erst, wenn Ihre tatsächlichen Anforderungen es erfordern.
16. Meine Produktions-Checkliste
Bevor Sie eine Node.js-Anwendung für die Produktion bereit halten, sollten Sie Folgendes überprüfen:
[ ] Production environment configured
[ ] Secrets stored securely
[ ] Database connection configured
[ ] Redis configured if required
[ ] Production build tested
[ ] Process manager configured
[ ] Application restart tested
[ ] Nginx configured
[ ] HTTPS configured
[ ] Firewall configured
[ ] Logs available
[ ] Error monitoring configured
[ ] Database backups configured
[ ] Backup restoration tested
[ ] Deployment process documented
[ ] CI/CD configured if needed
[ ] Health check endpoint available
17. Die Architektur, die ich im Hinterkopf habe
Eine grundlegende Produktionsumgebung für ein Node.js-System neigt dazu, so auszusehen:
┌─────────────┐
│ Internet │
└──────┬──────┘
│
▼
┌─────────────┐
│ Nginx │
│ SSL / Proxy│
└──────┬──────┘
│
┌─────────┴─────────┐
▼ ▼
┌────────────┐ ┌────────────┐
│ Node.js │ │ Node.js │
│ Instance 1 │ │ Instance 2 │
└─────┬──────┘ └─────┬──────┘
│ │
└─────────┬─────────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
PostgreSQL Redis External APIs
Um diesen Kern herum möchten Sie in der Regel Folgendes haben:
Monitoring
Logging
Backups
CI/CD
Security
Letzter Gedanke
Das Veröffentlichen einer Node.js-App in die Produktion ist niemals einfach nur:
npm start
Echte Produktivarbeit bedeutet, alles vorzusehen, was nach dem Start des Prozesses geschieht.
Was ist Ihr Plan, falls etwas unerwartet ausfällt?
Wie verhält sich das System, nachdem der Server neu gestartet wurde?
Wie reagiert das System, wenn die eingehende Trafficmenge plötzlich ansteigt?
Was passiert, sobald die Datenbank nicht mehr erreichbar ist?
Wie beheben Sie Schäden, wenn eine fehlerhafte Version veröffentlicht wird?
Welches ist das Vorgehen, falls Zugangsdaten versehentlich preisgegeben werden?
Das ist die Lücke zwischen:
„Es funktioniert auf meinem Rechner.“
und
„Es läuft zuverlässig in der Produktion.“
Man benötigt am Anfang keine riesige Infrastruktur. Beginnen Sie klein. Verstehen Sie jedes Element, das Sie hinzufügen. Automatisieren Sie alles, was sich wiederholt. Achten Sie auf die wirklich wichtigen Metriken. Fügen Sie Komplexität nur hinzu, wenn das System sie tatsächlich erfordert. Die Bereitstellung ist nicht das Ende der Entwicklung – sie ist der Punkt, an dem die eigentliche Nutzung des Softwareprodukts beginnt.
Zusätzliche Literatur
- Node.js Command Reference for Local Development and Production Servers – Eine übersichtliche Kommandoreferenz zu Node.js-Versionen, Paketverwaltern, Umgebungs-Einrichtung, Debugging, PM2 sowie Linux-Bereitstellungen ohne Ausfallzeiten.