بنقرة واحدة
portless-local
Replace port numbers with stable named .localhost URLs for local development
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Replace port numbers with stable named .localhost URLs for local development
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Verify understanding after implementation with targeted quizzes
Clarify requirements through targeted questions — uncovers unknown unknowns in specs
Remove AI-generated code patterns that don't match codebase style
Review code for quality, security, and best practices — read-only analysis
Create clear documentation for code and APIs
Audit code for security vulnerabilities — read-only analysis
| name | portless-local |
| description | Replace port numbers with stable named .localhost URLs for local development |
| license | MIT |
| compatibility | cline, claude, opencode, amp, codex, gemini, cursor, pi |
| hint | Use when you want clean, named URLs for local development instead of remembering port numbers |
| user-invocable | true |
| disable-model-invocation | true |
| metadata | {"audience":"all","workflow":"development"} |
Replace port numbers with stable, named .localhost URLs for local development. For humans and agents.
Note: By default, use HTTP (
http://myapp.localhost). Only enable HTTPS (--httpsorPORTLESS_HTTPS=1) if the user specifically requests it (e.g., for OAuth, secure cookies, or HTTPS-only features).
Local dev with port numbers is fragile. Portless fixes that by giving each dev server a stable, named .localhost URL.
| Problem | With Ports | With Portless |
|---|---|---|
| Port conflicts | Two projects on :3000 = EADDRINUSE | Auto-assigned ports, named URLs - no collisions |
| Memorizing ports | "Was the API on 3001 or 8080?" | Always http://api.localhost |
| Wrong app on refresh | Stop one server, start another on same port = confusion | Named URLs eliminate this |
| Monorepo chaos | Every service needs a unique port | Distinct hostnames for each service |
| Agent confusion | AI agents guess/hardcode wrong ports | http://myapp.localhost is deterministic |
| Cookie/storage clashes | Cookies bleed across ports on localhost | Each .localhost subdomain gets its own scope |
| Hardcoded config | CORS, OAuth, .env break when ports change | URLs are stable across restarts |
| Sharing URLs | "What port is that on?" in Slack | Everyone uses the same named URL |
| Browser history | localhost:3000 history is a jumble | Named URLs keep things organized |
# Global (recommended)
npm install -g portless
# Or as a project dev dependency
npm install -D portless
Note: portless is pre-1.0. When installed per-project, different contributors may run different versions.
Invoke via skill command or use CLI directly:
# Via skill command
/portless-local <NAME> <COMMAND> [OPTIONS]
# Or use CLI directly
portless <NAME> <COMMAND> [OPTIONS]
portless run [--name <name>] <cmd> [args...] # Infers name from package.json, git root, or directory
portless <name> <cmd> [args...] # Explicit name, no inference
portless run infers the project name from package.json, git root, or directory name. Use --name to override the inferred name while still applying worktree prefixes.
| Flag | Description |
|---|---|
--name <name> | Override the inferred base name (worktree prefix still applies). Only for portless run. |
--app-port <number> | Use a fixed port for the app instead of auto-assignment. Also configurable via PORTLESS_APP_PORT. |
--force | Override an existing route registered by another process |
Examples:
portless run next dev # Infer name from project
portless run --name myapp next dev # Override inferred name
portless myapp next dev # Explicit name
portless api pnpm start # API service
portless docs.myapp next dev # Subdomain
portless get <name>
Print the URL for a service. Useful for wiring services together in scripts or env vars:
BACKEND_URL=$(portless get backend)
Applies worktree prefix detection by default. Use --no-worktree to skip it.
portless alias <name> <port> # Register a static route
portless alias <name> <port> --force # Force override existing
portless alias --remove <name> # Remove the alias
Register a route for a service not managed by portless (e.g. a Docker container). Aliases persist across stale-route cleanup.
portless alias my-postgres 5432 # -> http://my-postgres.localhost
portless alias redis 6379 # -> http://redis.localhost
portless alias --remove my-postgres # Remove the alias
portless list
Shows active routes and their assigned ports.
portless trust
Adds the portless certificate authority to your system trust store. Required once for HTTPS with auto-generated certs.
If you skipped the trust prompt on first run, run portless trust to add the CA later.
HTTP/2 + TLS is enabled by default for faster dev server page loads.
Why HTTP/2 matters: Browsers limit HTTP/1.1 to 6 connections per host, which bottlenecks dev servers serving many unbundled files. HTTP/2 multiplexes all requests over a single connection.
First run: Generates a local CA and server certs, then adds the CA to your system trust store. After that, no prompts, no browser warnings.
Custom certificates: Use your own certs (e.g., from mkcert):
portless proxy start --cert ./cert.pem --key ./key.pem
Disable HTTPS: Use --no-tls to run with plain HTTP on port 80:
portless proxy start --no-tls
portless myapp next dev --no-tls
portless clean
Stops the proxy, removes the CA from OS trust store, deletes allowlisted files under ~/.portless, the system state directory, and removes the portless block from /etc/hosts. May prompt for elevated privileges.
portless proxy start
| Flag | Description |
|---|---|
-p, --port <number> | Proxy port (default: 443, or 80 with --no-tls). Auto-elevates with sudo. |
--no-tls | Disable HTTPS (use plain HTTP on port 80) |
--https | Enable HTTPS (default, accepted for compatibility) |
--lan | Enable LAN mode (mDNS .local domains for real device testing) |
--ip <address> | Override auto-detected LAN IP (use with --lan) |
--tld <tld> | Use a custom TLD instead of .localhost (e.g. .test) |
--cert <path> | Custom TLS certificate |
--key <path> | Custom TLS private key |
--foreground | Run in foreground instead of daemon mode |
portless proxy stop
Access services from phones and other devices on the same WiFi via mDNS (.local domains):
portless proxy start --lan
portless proxy start --lan --https
portless proxy start --lan --ip 192.168.1.42 # Manual IP override
Make it permanent by adding export PORTLESS_LAN=1 to your shell profile. Portless also remembers LAN mode via proxy.lan, so a stopped LAN proxy starts in LAN mode again.
Framework notes for LAN:
allowedDevOrigins: ['myapp.local', '*.myapp.local'] to next.config.js__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTSNSAllowsLocalNetworking to app.json for iOS ATSportless hosts sync # Add current routes to /etc/hosts
portless hosts clean # Remove portless entries from /etc/hosts
Auto-sync is on by default. Set PORTLESS_SYNC_HOSTS=0 to disable.
PORTLESS=0 pnpm dev
Runs the command directly without the proxy.
portless --help
portless --version
# Next.js
portless myapp next dev
# -> http://myapp.localhost
# Vite (auto-detected, --port injected)
portless myapp vite dev
# -> http://myapp.localhost
# Express
portless api node server.js
# -> http://api.localhost
# API service
portless api.myapp pnpm start
# -> http://api.myapp.localhost
# Documentation
portless docs.myapp next dev
# -> http://docs.myapp.localhost
# Admin dashboard
portless admin.myapp npm run dev
# -> http://admin.myapp.localhost
{
"scripts": {
"dev": "portless myapp next dev",
"dev:http": "portless myapp next dev --no-tls"
}
}
portless run auto-detects git worktrees. The branch name is prepended as a subdomain:
# Main worktree
portless run next dev
# -> http://myapp.localhost
# Linked worktree on branch "fix-ui"
portless run next dev
# -> http://fix-ui.myapp.localhost
Put portless run in your package.json once and it works everywhere - no collisions, no --force.
# Use .test TLD instead of .localhost
portless proxy start --tld test
portless myapp next dev
# -> http://myapp.test
Recommended TLDs:
.localhost - Default, auto-resolves to 127.0.0.1 in most browsers.test - IANA-reserved, no collision risk (recommended).local (conflicts with mDNS/Bonjour), .dev (Google-owned, forces HTTPS via HSTS)# Docker container running Postgres
portless alias my-postgres 5432
# -> http://my-postgres.localhost
# Redis server
portless alias redis 6379
# -> http://redis.localhost
# Get backend URL for frontend env
BACKEND_URL=$(portless get backend)
echo "VITE_API_URL=$BACKEND_URL" > .env.local
portless frontend vite dev
Browser (myapp.localhost) -> HTTP Proxy (port 80) -> App (random port 4000-4999)
http://<name>.localhost are proxied to the appPortless auto-detects and configures:
| Framework | Support | Notes |
|---|---|---|
| Next.js | ✅ Native | Respects PORT env var |
| Express | ✅ Native | Respects PORT env var |
| Nuxt | ✅ Native | Respects PORT env var |
| Vite | ✅ Injected | Auto-adds --port flag |
| Astro | ✅ Injected | Auto-adds --port flag |
| React Router | ✅ Injected | Auto-adds --port flag |
| Angular | ✅ Injected | Auto-adds --port and --host |
| Expo | ✅ Injected | Auto-adds --port and --host |
| React Native | ✅ Injected | Auto-adds --port and --host |
Portless is configured through environment variables. No config files needed.
| Variable | Description | Default |
|---|---|---|
PORTLESS_PORT | Proxy port | 443 (HTTPS) / 80 (HTTP) |
PORTLESS_HTTPS | HTTPS on by default; set to 0 to disable (same as --no-tls) | on |
PORTLESS_LAN | Set to 1 to always enable LAN mode (mDNS .local domains) | off |
PORTLESS_TLD | Use a custom TLD instead of .localhost (e.g. test) | localhost |
PORTLESS_APP_PORT | Use a fixed port for the app (skip auto-assignment) | random 4000-4999 |
PORTLESS_SYNC_HOSTS | Set to 0 to disable auto-sync of /etc/hosts | on |
PORTLESS_STATE_DIR | Override the state directory | see below |
PORTLESS | Set to 0 to bypass the proxy | enabled |
Portless stores state (routes, PID file, port file, TLS marker) in a directory that depends on the proxy port:
| Condition | Path |
|---|---|
| Port below 1024 (sudo, macOS/Linux) | /tmp/portless |
| Port 1024+ (no sudo) | ~/.portless |
| Windows (any port) | ~/.portless |
Override with PORTLESS_STATE_DIR.
| File | Purpose |
|---|---|
routes.json | Maps hostnames to ports |
routes.lock | Prevents concurrent writes |
proxy.pid | PID of the running proxy |
proxy.port | Port the proxy is listening on |
proxy.log | Proxy daemon log output |
proxy.lan | Remembers LAN mode and stores the last known LAN IP |
Apps get a random port in the 4000-4999 range. Portless sets PORT and usually HOST before running your command. Most frameworks respect PORT automatically. For frameworks that ignore it (Vite, Astro, React Router, Angular, Expo, React Native), portless auto-injects the right --port flag and, when needed, a matching --host flag.
# Portless auto-elevates with sudo, but if it fails:
sudo portless proxy start
# Or use HTTP mode on a different port
portless myapp next dev --no-tls -p 8080
Trust the local CA on first run. Run portless trust if needed.
# Each worktree gets unique subdomain automatically
# Or use different names:
portless myapp-v2 next dev
| Tool | Type | URLs | Use Case |
|---|---|---|---|
| portless | Local proxy | http://myapp.localhost | Clean local dev URLs |
| ngrok | Public tunnel | https://random.ngrok.io | Share with others |
| cloudflared | Public tunnel | https://myapp.trycloudflare.com | Share with others |