portless myapp next dev# Registers: myapp.localhost -> port 4123
3
Access any subdomain
4
# All of these route to the same app:http://myapp.localhost:1355http://tenant1.myapp.localhost:1355http://tenant2.myapp.localhost:1355http://anything.myapp.localhost:1355
Longer suffix match: If both myapp.localhost and api.myapp.localhost are registered, tenant1.api.myapp.localhost routes to api.myapp.localhost (longer suffix wins)
function findRoute( routes: { hostname: string; port: number }[], host: string): { hostname: string; port: number } | undefined { return ( // 1. Exact match first routes.find((r) => r.hostname === host) || // 2. Wildcard match (longest suffix) routes.find((r) => host.endsWith("." + r.hostname)) );}
Wildcard routing is ideal for multi-tenant apps where each tenant gets their own subdomain:
portless myapp next dev# -> http://myapp.localhost:1355# Tenants access via their subdomain:# http://acme.myapp.localhost:1355# http://globex.myapp.localhost:1355# http://initech.myapp.localhost:1355
Your app can extract the tenant ID from the Host header:
You can also use portless run to infer the service name from package.json. If you set "name": "frontend", then portless run next dev will automatically use frontend.localhost as the hostname.
When one service needs to call another, use the portless URL:
// app/page.tsxexport default async function Page() { // Call the API service const res = await fetch("http://api.myapp.localhost:1355/users"); const users = await res.json(); return <UserList users={users} />;}
When proxying between portless apps, always set changeOrigin: true. Without it, the proxy forwards the original Host header, causing portless to route the request back to the frontend in an infinite loop.Portless detects this and responds with 508 Loop Detected along with a helpful error message.
DNS labels (the parts between dots) are limited to 63 characters per RFC 1035.Portless automatically truncates long labels and appends a hash suffix for uniqueness:
export function truncateLabel(label: string): string { if (label.length <= MAX_DNS_LABEL_LENGTH) return label; // 6-char hex hash from the full label for uniqueness const hash = createHash("sha256").update(label).digest("hex").slice(0, 6); // Reserve space for "-" separator + 6-char hash = 7 chars const maxPrefixLength = MAX_DNS_LABEL_LENGTH - 7; const prefix = label.slice(0, maxPrefixLength).replace(/-+$/, ""); return `${prefix}-${hash}`;}
Example:
portless this-is-a-very-long-service-name-that-exceeds-the-dns-label-limit next dev# -> http://this-is-a-very-long-service-name-that-exceeds-the-dns-la-a1b2c3.localhost:1355
Use portless alias to register routes for services not managed by portless (e.g., Docker containers, databases):
# Register a PostgreSQL containerportless alias postgres 5432# -> http://postgres.localhost:1355 routes to localhost:5432# Register a Redis containerportless alias redis 6379# -> http://redis.localhost:1355 routes to localhost:6379# Remove an aliasportless alias --remove postgres
Aliases show up in portless list with (alias) instead of a PID:
portless myapp next dev# Registers: myapp.localhost# All of these route to the same app:http://a.myapp.localhost:1355http://a.b.myapp.localhost:1355http://a.b.c.myapp.localhost:1355