portless-local
$
npx mdskill add jellydn/my-ai-tools/portless-localGenerate stable .localhost URLs to eliminate port conflicts.
- Removes the need to remember or manage specific port numbers.
- Integrates with any local development server environment.
- Automatically assigns unique ports to prevent address collisions.
- Delivers clean, human-readable URLs for consistent access.
SKILL.md
.github/skills/portless-localView on GitHub ↗
---
name: portless-local
description: Named .localhost URLs for local development - replaces port numbers with stable, readable URLs
license: MIT
compatibility: 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
metadata:
audience: all
workflow: development
---
# Portless - Named .localhost URLs
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 (`--https` or `PORTLESS_HTTPS=1`) if the user specifically requests it (e.g., for OAuth, secure cookies, or HTTPS-only features).
## Why Portless?
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 |
## Installation
```bash
# 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.
## Usage
Invoke via skill command or use CLI directly:
```bash
# Via skill command
/portless-local <NAME> <COMMAND> [OPTIONS]
# Or use CLI directly
portless <NAME> <COMMAND> [OPTIONS]
```
## Commands
### Run an App
```bash
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:**
```bash
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
```
### Get a Service URL
```bash
portless get <name>
```
Print the URL for a service. Useful for wiring services together in scripts or env vars:
```bash
BACKEND_URL=$(portless get backend)
```
Applies worktree prefix detection by default. Use `--no-worktree` to skip it.
### Alias (Static Routes)
```bash
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.
```bash
portless alias my-postgres 5432 # -> http://my-postgres.localhost
portless alias redis 6379 # -> http://redis.localhost
portless alias --remove my-postgres # Remove the alias
```
### List Routes
```bash
portless list
```
Shows active routes and their assigned ports.
### Trust the CA
```bash
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.
### HTTPS & HTTP/2
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):
```bash
portless proxy start --cert ./cert.pem --key ./key.pem
```
**Disable HTTPS:** Use `--no-tls` to run with plain HTTP on port 80:
```bash
portless proxy start --no-tls
portless myapp next dev --no-tls
```
### Clean Up
```bash
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.
### Proxy Control
#### Start Proxy
```bash
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 |
#### Stop Proxy
```bash
portless proxy stop
```
### LAN Mode
Access services from phones and other devices on the same WiFi via mDNS (`.local` domains):
```bash
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:**
- **Next.js:** Add `allowedDevOrigins: ['myapp.local', '*.myapp.local']` to `next.config.js`
- **Vite / React Router / SvelteKit / Astro:** Handled automatically via `__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS`
- **Expo / React Native:** Add `NSAllowsLocalNetworking` to `app.json` for iOS ATS
### Hosts
```bash
portless 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.
### Bypass Portless
```bash
PORTLESS=0 pnpm dev
```
Runs the command directly without the proxy.
### Info
```bash
portless --help
portless --version
```
## Common Use Cases
### 1. Basic Development Server
```bash
# 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
```
### 2. Multiple Services with Subdomains
```bash
# 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
```
### 3. Use in package.json
```json
{
"scripts": {
"dev": "portless myapp next dev",
"dev:http": "portless myapp next dev --no-tls"
}
}
```
### 4. Git Worktree Support
`portless run` auto-detects git worktrees. The branch name is prepended as a subdomain:
```bash
# 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`.
### 5. Custom TLD
```bash
# 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)
- **Avoid:** `.local` (conflicts with mDNS/Bonjour), `.dev` (Google-owned, forces HTTPS via HSTS)
### 6. Static Aliases for External Services
```bash
# Docker container running Postgres
portless alias my-postgres 5432
# -> http://my-postgres.localhost
# Redis server
portless alias redis 6379
# -> http://redis.localhost
```
### 7. Wire Services Together
```bash
# Get backend URL for frontend env
BACKEND_URL=$(portless get backend)
echo "VITE_API_URL=$BACKEND_URL" > .env.local
portless frontend vite dev
```
## How It Works
```
Browser (myapp.localhost) -> HTTP Proxy (port 80) -> App (random port 4000-4999)
```
1. Portless runs an HTTP reverse proxy on port 80 (or HTTPS on 443 if enabled)
2. Each app registers a route mapping hostname to assigned port
3. Requests to `http://<name>.localhost` are proxied to the app
4. Optional HTTPS: Auto-generates local CA and trusts it on first run
5. Auto-elevates with sudo on macOS/Linux for port binding
## Framework Support
Portless 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` |
## Configuration
Portless is configured through environment variables. No config files needed.
### Environment Variables
| 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 |
### State Directory
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`.
### State Files
| 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 |
### Port Assignment
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.
## Troubleshooting
### Port 443 permission denied
```bash
# 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
```
### Certificate warning
Trust the local CA on first run. Run `portless trust` if needed.
### Name collision
```bash
# Each worktree gets unique subdomain automatically
# Or use different names:
portless myapp-v2 next dev
```
## Comparison
| 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 |
## Related
- [portless.sh](https://portless.sh/) - Official documentation
- [vercel-labs/portless](https://github.com/vercel-labs/portless) - Official skill for Claude Code
More from jellydn/my-ai-tools
- adrManages Architecture Decision Records (ADR) for tracking important architectural decisions
- codemapOrchestrate parallel codebase analysis to produce 7 structured documents about the codebase in .planning/codebase/
- handoffsCreates detailed handoff plans of conversations for continuing work in new sessions
- pickupResumes work from a previous handoff session which are stored in `.claude/handoffs/`
- qmd-knowledgeProject-specific knowledge management system using qmd MCP server. Captures learnings, issue notes, and conventions in a searchable knowledge base.
- slopRemoves AI-generated code slop from git diffs to maintain code quality