Skip to main content

Schluss mit dem Port-Chaos: Lokale Entwicklung mit Portless

Es ist Dienstagvormittag, du startest dein Frontend und das Terminal meldet: "Port 3000 is already in use". Natürlich. Im Hintergrund läuft noch das andere Projekt, das du vor einer Stunde nur kurz anschauen wolltest. Also weichst du auf 3001 aus. Kurz darauf braucht die API ebenfalls die 3000, das Admin-Panel sitzt auf 5173 und irgendwo läuft noch ein Storybook auf 6006.

Nach ein paar Wochen mit mehreren Projekten stellst du dir eine Frage, die eigentlich lächerlich ist: Welche Nummer gehörte noch mal zu welcher App? Der Browser-Verlauf besteht nur noch aus localhost-Einträgen, die Lesezeichen sind wertlos, und im Team steht in der README ein Satz wie "läuft auf Port 3000, wenn der nicht belegt ist".

Genau an diesem Punkt setzt Portless an. Statt Portnummern bekommt jedes Projekt einen festen, sprechenden Namen, zum Beispiel https://myapp.localhost. In diesem Beitrag schauen wir uns an, wie das funktioniert, was es im Alltag bringt und wo die Grenzen liegen.

Warum Ports mehr nerven als gedacht

Das Problem ist banal, aber es kostet täglich kleine Portionen Konzentration. Jedes Framework bringt einen Standardport mit: Next.js und Nuxt starten auf 3000, Vite auf 5173, Angular auf 4200, Astro auf 4321, Storybook auf 6006. Wer zwei Projekte mit demselben Stack parallel betreibt, läuft zwangsläufig in Kollisionen.

Die Folgen gehen über die Fehlermeldung hinaus:

  • Wechselnde Nummern: Ein Projekt läuft heute auf 3000, morgen auf 3002, weil gestern noch etwas offen war. Alles, was die Nummer kennt, bricht.
  • Hartcodierte Konfiguration: OAuth-Redirect-URIs, CORS-Einstellungen, Webhook-Ziele und .env-Dateien enthalten oft feste Ports. Ändert sich der Port, ändert sich die Konfiguration.
  • Geteilte Cookies: Alle Apps auf localhost teilen sich denselben Cookie-Bereich, egal auf welchem Port sie laufen. Ein Login im einen Projekt kann die Session im anderen überschreiben. Solche Fehler sind schwer zu finden.
  • Mentale Last: Du merkst dir Zuordnungen, die keinen Informationswert haben. "5173 ist das Dashboard" ist Wissen, das niemand braucht.

Dazu kommt ein neuer Faktor: KI-Agenten, die lokal Dev-Server starten und im Browser testen. Auch sie müssen wissen, unter welcher Adresse eine App gerade läuft. Ein stabiler Name ist für Menschen und Agenten gleichermaßen einfacher als eine Zahl, die sich ändern kann.

Die Idee hinter Portless

Portless löst das Problem, indem es die Ports aus deinem Blickfeld nimmt. Im Hintergrund läuft ein lokaler Reverse-Proxy, der auf Port 443 lauscht. Jede App registriert dort einen Hostnamen, und der Proxy leitet Anfragen an den passenden Port weiter.

Browser (myapp.localhost) -> Proxy (Port 443) -> App (zufälliger Port)

Den eigentlichen Port wählt Portless selbst, aus dem Bereich 4000 bis 4999, und reicht ihn der App über die Umgebungsvariable PORT weiter. Du musst ihn weder kennen noch eintippen. Der Name bleibt stabil, die Nummer dahinter darf sich ändern.

Dass das ohne Eingriffe in System-DNS funktioniert, liegt an der Domain-Endung .localhost. Namen mit dieser Endung lösen auf aktuellen Systemen automatisch auf 127.0.0.1 auf. Es braucht also keinen Eintrag in der Hosts-Datei und keinen lokalen DNS-Server.

Ein Sicherheitsaspekt gleich vorweg: Der Proxy lauscht standardmäßig nur auf den Loopback-Adressen 127.0.0.1 und ::1. Er ist also nicht aus dem Netzwerk erreichbar, es sei denn, du aktivierst den LAN-Modus ausdrücklich.

Installation und erster Start

Die Einstiegshürde ist niedrig. Voraussetzung ist Node.js 24 oder neuer, unterstützt werden macOS, Linux und Windows. Installiert wird per npm, am besten global:

npm install -g portless

Alternativ geht es als Dev-Dependency pro Projekt. Dann können aber verschiedene Teammitglieder unterschiedliche Versionen nutzen, und das ist bei einem Tool vor Version 1.0 nicht ganz unkritisch (mehr dazu im Abschnitt zu den Stolpersteinen).

Im Projektordner reicht danach ein einziger Befehl:

portless

Portless liest das dev-Script aus deiner package.json und startet es über den Proxy. Den Namen leitet es aus der package.json, dem Git-Root oder dem Ordnernamen ab. Willst du einen anderen, legst du eine portless.json an:

{ "name": "myapp" }

Wer es lieber explizit mag, gibt Namen und Befehl direkt an:

portless myapp next dev
# -> https://myapp.localhost

Das lässt sich auch fest in die package.json schreiben, sodass im Team alle denselben Befehl nutzen: "dev": "portless myapp next dev".

Was beim ersten Start passiert: Portless erzeugt eine lokale Zertifizierungsstelle (CA), richtet sie als vertrauenswürdig ein und bindet Port 443. Dafür fragt es unter macOS und Linux nach Administratorrechten. Das passiert einmalig und ist bei HTTPS lokal unvermeidlich.

Und die Frameworks? Next.js, Express oder Nuxt respektieren die Variable PORT ohnehin. Für Vite, Astro, Angular, Expo und andere, die sie ignorieren, hängt Portless automatisch das passende --port-Flag an (bei Bedarf auch --host). Du musst dein Script dafür nicht anfassen.

Subdomains für mehrere Services

Selten besteht ein Projekt nur aus einer App. Meist gibt es ein Frontend, eine API, vielleicht eine Doku-Seite oder ein Admin-Panel. Mit Portless bekommt jeder Dienst einen Namen unter dem Projektnamen:

portless api.myapp pnpm start
# -> https://api.myapp.localhost

portless docs.myapp next dev
# -> https://docs.myapp.localhost

Das ist mehr als Kosmetik. Die Struktur spiegelt wider, wie das Projekt in Produktion aufgebaut ist. Ein Frontend, das die API unter api.myapp.localhost anspricht, ähnelt dem späteren api.deine-domain.de sehr viel stärker als ein Aufruf von localhost:4000. Dadurch lassen sich Cookie-Domains, CORS-Regeln und Redirect-URIs deutlich realistischer testen.

Und noch ein Nebeneffekt: Die Cookie-Probleme von oben verschwinden. Jeder Hostname hat seinen eigenen Cookie-Bereich, die Apps stören sich nicht mehr gegenseitig.

Git Worktrees ohne Kollisionen

Wer mit Git Worktrees arbeitet, etwa um parallel an einem Bugfix und einem Feature zu sitzen, kennt das Problem in verschärfter Form: Beide Checkouts enthalten denselben Code und wollen denselben Port.

Portless erkennt Worktrees automatisch, wenn du portless run nutzt. Im Hauptverzeichnis bleibt der Name unverändert, in einem verknüpften Worktree wird der Branch-Name als Subdomain vorangestellt:

# Haupt-Worktree
portless run next dev   # -> https://myapp.localhost

# Worktree auf Branch "fix-ui"
portless run next dev   # -> https://fix-ui.myapp.localhost

Du trägst portless run einmal in die package.json ein, und es funktioniert überall. Keine Konfiguration pro Branch, kein --force, keine Kollisionen. Mit --name überschreibst du bei Bedarf den Basisnamen, das Worktree-Präfix bleibt erhalten.

Gerade wenn du neben deinem eigenen Code auch KI-Agenten in separaten Worktrees arbeiten lässt, ist das sehr praktisch. Jede Instanz hat ihre eigene, vorhersagbare Adresse.

HTTPS lokal und eigene Domain-Endung

HTTPS ist bei Portless standardmäßig aktiv, inklusive HTTP/2. Das ist weniger selbstverständlich, als es klingt. Viele Browser-Funktionen (Service Worker, bestimmte APIs, Secure-Cookies) verhalten sich über HTTP anders als in Produktion. Wer lokal gleich mit HTTPS arbeitet, entdeckt solche Unterschiede früher. Brauchst du doch einfaches HTTP, schaltest du mit --no-tls um.

Standardmäßig nutzt Portless .localhost. Du kannst die Endung aber ändern:

portless proxy start --tld test
portless myapp next dev
# -> https://myapp.test

Der Proxy pflegt dafür die Hosts-Datei selbst, weil .test anders als .localhost nicht automatisch aufgelöst wird. Mehrere Endungen gleichzeitig sind ebenfalls möglich, indem du --tld wiederholst.

Besonders interessant: Als "TLD" lässt sich auch eine Domain nutzen, die dir gehört, etwa dev.example.com. Dann heißt deine App lokal https://myapp.dev.example.com. Die URLs haben dieselbe Struktur wie in Produktion, und OAuth-Redirects, Cookies über Subdomains hinweg und hostbasiertes Routing verhalten sich in beiden Umgebungen gleich.

Zur Wahl der Endung gibt es eine klare Empfehlung: .test ist von der IANA reserviert und kollidiert deshalb nie mit echten Domains. Meiden solltest du .local (Konflikte mit mDNS/Bonjour) und .dev (gehört Google und erzwingt HTTPS per HSTS).

Stolpersteine und Trade-offs

Kein Werkzeug ist ohne Haken. Diese Punkte solltest du vorher kennen:

  • Administratorrechte: Port 443 und die lokale CA brauchen erhöhte Rechte. Auf gemanagten Firmenrechnern kann das eine Hürde sein, die erst mit der IT geklärt werden muss.
  • Pre-1.0: Portless ist vor Version 1.0. Das Format des internen Zustandsordners kann sich zwischen Releases ändern, dann musst du eventuell portless trust erneut ausführen. Eine globale Installation vermeidet unterschiedliche Versionen im Team.
  • Node.js 24 oder neuer: Wer noch auf einer älteren Node-Version festhängt, muss zuerst updaten.
  • Komplexe Scripts: Bei zusammengesetzten Befehlen (&&, Pipes, NODE_ENV=production vite) oder Weiterleitungen auf andere Scripts fügt Portless keine Port-Flags ein. Dort setzt du den Port selbst.
  • Prozessgrenzen: Ein Reverse-Proxy ist eine zusätzliche Schicht. Läuft etwas schief, gibt es eine Stelle mehr, an der du suchen musst.

Nichts davon ist ein Ausschlusskriterium. Aber es lohnt sich, das Tool zuerst in einem Projekt auszuprobieren und nicht gleich im ganzen Team auszurollen.

Fazit: Namen statt Nummern

Portless löst ein Problem, das viele von uns so lange mit sich herumtragen, dass es kaum noch auffällt. Ein stabiler Name pro App, HTTPS ohne Bastelei, saubere Subdomains und ein Worktree-Support, der einfach funktioniert: Das reduziert die kleinen Reibungen im Entwicklungsalltag spürbar.

Der Aufwand zum Ausprobieren ist gering. Du installierst es global, ersetzt in einem Projekt "dev": "next dev" durch portless und merkst nach dem ersten Start, ob dir die neue Arbeitsweise liegt. Wenn du regelmäßig mehrere Projekte parallel betreibst, mit Worktrees oder Agenten arbeitest oder lokal möglichst nah an der Produktion testen willst, lohnt sich der Versuch.

Dein nächster Schritt: Wähle ein Projekt, installiere Portless und starte es mit einem Namen deiner Wahl. Mehr braucht es nicht.

Du möchtest deine lokale Entwicklungsumgebung und Workflows im Team aufräumen oder brauchst Unterstützung bei einem Webprojekt? Mehr dazu findest du unter Webentwicklung, oder du schreibst mir direkt.