Zum Inhalt springen

Node.js

Der Node.js-Container (envoryx-<projekt>-node, Image ghcr.io/envoryx/envoryx-node:<version>) bringt npm, pnpm und yarn mit und kann zwei ziemlich unterschiedliche Rollen spielen.

Neben PHP, Python, Go, Ruby, Java oder .NET ist er dein Werkzeugkasten: Er baut mit npm run build die Assets oder betreibt einen Vite-Dev-Server fürs Frontend, während das Backend sein eigenes Ding macht. In einem Node.js-Projekt - mit einer Node.js-Vorlage oder Node.js-Anwendung für ein leeres Projekt - ist er die Anwendung, und PHP brauchst du gar nicht.

Standard ist 24, weil das die aktuelle LTS ist. 22 ist ebenfalls LTS, und 26 gibt es, wenn du die neuesten Funktionen willst. 20 und 18 stehen noch für Projekte bereit, die nicht umziehen können, haben ihr Lebensende aber hinter sich.

Wechselst du die Version, kommen die Container mit dem neuen Node.js zurück, node_modules bleibt aber, wie es ist. Reinen JavaScript-Paketen ist das egal. Native Module (bcrypt, better-sqlite3 und Co.) wurden für die alte Version gebaut, deshalb führst du danach im Terminal npm rebuild aus (oder installierst neu).

Der Container hat kein eigenes NODE_ENV. Ein npm run build aus dem Bereich Aktionen oder dem Terminal baut deshalb so, wie die Werkzeuge es von sich aus tun, bei Next.js und Vite also ein echtes Produktions-Bundle, und npm install installiert trotzdem deine devDependencies. Nur der Dev-Server setzt für sich selbst NODE_ENV=development (außer du setzt im Bereich Umgebung ein eigenes), und ein Produktions-Build läuft mit NODE_ENV=production. Worker und Cron-Jobs richten sich nach dem Dev-Server: Sie bekommen production, solange er einen Produktions-Build ausliefert, sonst development, und in der Entwicklung gewinnt auch bei ihnen ein NODE_ENV, das du selbst setzt. Im Produktionsmodus ist es immer production.

pnpm und yarn kommen über Corepack, und yarn ist das aktuelle Yarn (4.x), nicht Yarn 1. Nutzt dein Projekt noch Yarn 1, legst du das in der package.json mit "packageManager": "yarn@1.22.22" fest, und Corepack holt diese Version für das Projekt.

Eine Compiler-Toolchain hat das Image nicht (kein Python, kein make, kein g++). Native Module funktionieren, solange sie ein vorgebautes Binary für deine Node.js-Version und Linux mitbringen, was die verbreiteten tun. Muss ein Paket mit node-gyp aus dem Quellcode bauen, scheitert es mit etwas wie gyp ERR! find Python. Dann hilft meist eine Version des Pakets, die für deine Node.js-Version ein Prebuild hat, oder du bleibst bei der Node.js-Version, für die es gemacht ist. Muss das Paket wirklich kompilieren, erledigt das ein eigenes Image mit build-essential und Python auf Basis des Envoryx-Images.

Schalte beim Node.js-Dienst Dev-Server ausführen ein, im Assistenten oder im Bereich Laufzeit. Der Container führt dann ein Skript aus deiner package.json (dev, wenn du nichts anderes wählst) als Hauptprozess aus und startet es neu, falls es abstürzt.

Die Karte Node.js im Bereich Laufzeit mit der Version, dem laufenden Dev-Server, dem Next.js-Preset, Paketmanager, Skript und PortDie Karte Node.js im Bereich Laufzeit mit der Version, dem laufenden Dev-Server, dem Next.js-Preset, Paketmanager, Skript und Port

Jedes Framework will Host und Port anders mitgeteilt bekommen, deshalb gibt es Vorlagen:

Vorlage Was Envoryx übergibt Standard-Port
Vite --host 0.0.0.0 --port 5173
Next.js -H -p 3000
Nuxt --host --port 3000
Sonstige setzt nur HOST und PORT -

Bei npm stehen die Flags hinter einem -- (npm run dev -- --host 0.0.0.0 …), das npm selbst schluckt. pnpm und Yarn würden dieses -- ans Skript weiterreichen, und Vite ignorierte dann alles dahinter und lauschte nur auf localhost. Bei diesen beiden folgen die Flags deshalb direkt auf den Skriptnamen.

Wie Laravels Vite-Plugin hinter dem Dev-Host funktioniert, steht auf der PHP-Seite: Die Adresse des Dev-Servers bekommt jeder Container des Projekts als ENVORYX_DEV_URL.

In einem Node.js-Projekt ohne PHP schickt der Proxy https://<projekt>.<basis>, jede zusätzliche Domain und https://<projekt>-dev.<basis> zum Dev-Server - Hot Module Reloading über WebSockets inklusive. Außerdem bekommt der Container einen eigenen Host-Port (Bereich Domains → Direktzugriff), der auch ohne eingerichtetes DNS funktioniert.

Neben PHP ist es umgekehrt: PHP bleibt die Anwendung, und der Dev-Server behält https://<projekt>-dev.<basis>.

Solange der Dev-Server das Projekt ausliefert, ist der Port des Webserver-Containers bewusst nicht veröffentlicht. Sonst wäre der Document Root - meist das ganze Projekt samt .env und Quellcode - im Netz lesbar. Schaltest du den Dev-Server aus, kommt der Port mit derselben Nummer zurück. Preset, Skript und Port des Dev-Servers bleiben gespeichert, solange er aus ist, und auch seinen Host-Port behält er. Schaltest du ihn wieder ein (in der Oberfläche, über API oder CLI), ist er genau so zurück, wie er war.

Vite lehnt Anfragen für Hostnamen ab, die es nicht kennt. Envoryx setzt __VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS=.<basis> und lässt damit <projekt>.<basis>, <projekt>-dev.<basis> und jede weitere Domain unter deiner Basisdomain durch. Nimmst du eine Domain außerhalb der Basisdomain dazu, trag sie in server.allowedHosts in deiner vite.config ein. Nuxt prüft Hosts nicht. Next.js ab 15 schaut dagegen, woher Dev-Anfragen kommen, und Fast Refresh verbindet sich nie von einem Origin, der in allowedDevOrigins fehlt. Das Next.js-Template schreibt diese Liste für dich in die next.config.ts: *.<basis>, das das Projekt, seinen Dev-Host und eine spätere Umbenennung abdeckt, dazu die zusätzlichen Domains, die das Projekt beim Anlegen hat. Hängst du später eine Domain außerhalb der Basisdomain an, trägst du sie dort selbst nach. Bei einer Next.js-App, die du mitgebracht hast, ist das typische Zeichen für eine fehlende Liste: Die Seite lädt hinter einer Projektdomain, aber das Hot Reloading bleibt stumm.

Ein ganz neues Node.js-Projekt hat noch keine package.json, es gibt also nichts zu starten. Statt dauernd abzustürzen, wartet der Dev-Server und sagt das im Log (envoryx: waiting for package.json …). Das gilt auch für einen Dev-Server neben PHP.

Gibt es eine package.json mit Abhängigkeiten, aber kein node_modules, etwa direkt nach einem Klon, installiert der Dev-Server sie, bevor er startet. Das tut er nur aus einem Lockfile und im Frozen-Modus: npm ci bei package-lock.json, pnpm install --frozen-lockfile bei pnpm-lock.yaml und yarn install --frozen-lockfile bei yarn.lock. So bekommst du genau das, was das Repository festschreibt, und Envoryx schreibt nie in eine deiner Dateien. Ohne Lockfile, oder wenn die Installation scheitert, wartet der Dev-Server stattdessen (envoryx: waiting for node_modules …), und die Projektübersicht sagt dir das. Dann führst du die Installation im Bereich Aktionen aus, und der Server startet, sobald sie fertig ist. Steht in der Node.js-Karte pnpm oder Yarn als Paketmanager, sind deren Aktionen install und build von Anfang an da, auch ohne Lockfile, damit ein frisches pnpm- oder Yarn-Projekt nicht mit einer package-lock.json endet. Die Aktionen und envoryx project exec laufen mit CI=1, und das allein brächte Yarn 2+ und pnpm dazu, das Lockfile nicht anzufassen; Envoryx schaltet das für sie ab, damit yarn install eine neue yarn.lock schreiben oder einer bestehenden eine Abhängigkeit hinzufügen kann. Worker warten ebenfalls auf die Abhängigkeiten, installieren sie aber nie selbst, damit sich nicht zwei Installationen in die Quere kommen. Templates installieren ihre Abhängigkeiten schon beim Anlegen.

Manchmal willst du sehen, wie sich die App verhält, wenn sie richtig gebaut ist. Stell den Modus im Bereich Laufzeit auf Produktions-Build: Dann führt jeder Containerstart das Build-Skript (standardmäßig build) und danach das Serve-Skript (start, bei Vite und Nuxt preview) mit NODE_ENV=production aus. Beides sind Namen von Skripten aus deiner package.json, keine Befehle. Willst du einen Nuxt-Build etwa mit node .output/server/index.mjs ausliefern, legst du dafür ein start-Skript an und wählst es. Für eine Next.js- oder Nuxt-App mit Server-Side-Rendering ist das ein produktionsnaher Lauf hinter denselben URLs.

Wird deine App zu reinen Dateien gebaut, brauchst du gar keinen Node.js-Server. Schalte den Dev-Server aus, stell den Document Root auf die Build-Ausgabe (dist bei Vite - das Template macht das schon - oder out bei einem statischen Next.js-Export) und führ npm run build aus. Bei clientseitigem Routing schaltest du in der Webserver-Karte zusätzlich SPA-Fallback auf index.html ein, damit ein Neuladen auf /settings nicht mit einem 404 endet.

Node.js-Inspector-Port veröffentlichen im Bereich Laufzeit (braucht den Dev-Server) legt Port 9229 auf einen eigenen Host-Port. Der Bereich IDE zeigt Host, Port, Pfadzuordnung und Beispiele für die package.json.

Envoryx setzt NODE_OPTIONS=--inspect absichtlich nicht am Container: npm ist selbst ein Node-Prozess und würde sich den Port schnappen, bevor deine App dran ist. Starte den Inspector stattdessen in deinem eigenen Skript, etwa mit NODE_OPTIONS='--inspect=0.0.0.0:9229' nuxt dev oder node --inspect=0.0.0.0:9229 server.js, und verbinde dann WebStorm (Attach to Node.js/Chrome) oder VS Code (request: attach).

Next.js tanzt aus der Reihe: Es öffnet den Inspector seines Server-Prozesses, in dem dein Server-Code läuft, einen Port über dem in --inspect. Starte es also einen Port unter dem veröffentlichten, beim Standardport 9229 mit NODE_OPTIONS='--inspect=0.0.0.0:9228' next dev; der Server-Prozess landet dann auf 9229, und dorthin führt der Host-Port. Das Beispiel im Bereich IDE macht genau das, für jeden Wert von Inspector-Port im Container.

Als Templates gibt es Vite + React (TypeScript), Next.js (App Router, TypeScript) und Nuxt.

Im Bereich Aktionen findest du node -v und die Installations- und Build-Befehle von npm (install, ci, run build), pnpm und yarn. Die von pnpm und Yarn brauchen ihre Lock-Datei, außer der Paketmanager des Projekts steht auf genau diesem (siehe oben).

Der Bereich Tests erkennt die test-Skripte deiner package.json, Playwright und Cypress; siehe Aktionen und Tests. Und für alles, was dauerhaft laufen soll, gibt es die Worker npm-Script (npm run <name>) und Node.js-Script (node <datei>), beschrieben unter Worker und Cron-Jobs.