Skip to main content

portless

Set up and use portless for named local dev server URLs (e.g. https://myapp.localhost instead of http://localhost:3000). Use when integrating portless into a project, configuring dev server names, setting up the local proxy, working with .localhost domains, or troubleshooting port/proxy issues.

소스 정보

저장소
vercel-labs/portless
최근 소스 활동
2026년 10월 2일 04:50
감지된 SKILL.md 언어
영어
스타
12,649
포크
433

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
portless
description
Set up and use portless for named local dev server URLs (e.g. https://myapp.localhost instead of http://localhost:3000). Use when integrating portless into a project, configuring dev server names, setting up the local proxy, working with .localhost domains, or troubleshooting port/proxy issues.
# Portless Replace port numbers with stable, named .localhost URLs. For humans and agents. ## Why portless - **Port conflicts**: `EADDRINUSE` when two projects default to the same port - **Memorizing ports**: which app is on 3001 vs 8080? - **Refreshing shows the wrong app**: stop one server, start another on the same port, stale tab shows wrong content - **Monorepo multiplier**: every problem scales with each service in the repo - **Agents test the wrong port**: AI agents guess or hardcode the wrong port - **Cookie/storage clashes**: cookies on `localhost` bleed across apps; localStorage lost when ports shift - **Hardcoded ports in config**: CORS allowlists, OAuth redirects, `.env` files break when ports change - **Sharing URLs with teammates**: "what port is that on?" becomes a Slack question - **Browser history is useless**: `localhost:3000` history is a mix of unrelated projects ## Installation Install globally (recommended) or as a project dev dependency. Do NOT use `npx` or `pnpm dlx` for one-off execution. ```bash # Global (available everywhere) npm install -g portless # Or per-project dev dependency npm install -D portless ``` When installed per-project, invoke via package.json scripts or `npx portless` (since the package is local, npx will not download anything). ## Quick Start ```bash # Install globally (or add -D to a project) npm install -g portless # Run your app (auto-starts the HTTPS proxy on port 443) portless run next dev # -> https://<project>.localhost # Or with an explicit name portless myapp next dev # -> https://myapp.localhost ``` The proxy auto-starts when you run an app. You can also start it explicitly with `portless proxy start`. Auto-start reuses the configuration (port, TLS, TLDs) from the most recent proxy run, so a restart or reboot does not silently revert to defaults. Explicit env vars always take priority. In non-interactive environments (no TTY, or `CI=1`), portless exits with a descriptive error instead of prompting. Task runners like turborepo should pre-start the proxy. ## Integration Patterns ### Zero-config (recommended) Bare `portless` works out of the box. It runs the `"dev"` script from `package.json` through the proxy, inferring the app name from the package name, git root, or directory: ```bash portless # -> runs "dev" script, https://<project>.localhost pnpm dev # -> works without portless, plain "next dev" ``` Use an optional `portless.json` to override defaults (name, script, port): ```json { "name": "myapp" } ``` ```bash portless # -> runs "dev" script, https://myapp.localhost ``` ### Monorepo One `portless.json` at the repo root. Portless discovers packages from `pnpm-workspace.yaml`, or the `"workspaces"` field in `package.json` (npm, yarn, bun): ```json { "apps": { "apps/web": { "name": "myapp" }, "apps/api": { "name": "api.myapp" } } } ``` ```bash portless # from repo root: start all packages with a "dev" script cd apps/web && portless # start just one package portless --script start # run "start" instead of "dev" ``` The `apps` map is optional and only provides name overrides. Unlisted packages auto-discover with inferred names. Without an `apps` map, hostnames follow `<package>.<project>.localhost`. The project name comes from the most common npm scope (e.g. `@myorg/web` and `@myorg/api` produce `myorg`), falling back to the workspace root directory name. If a package's short name matches the project name, it uses the bare `<project>.localhost`. ### Turborepo For turborepo projects, use portless as the `dev` script with the real command in a separate script: ```json { "scripts": { "dev": "portless", "dev:app": "next dev" }, "portless": { "name": "myapp", "script": "dev:app" } } ``` `pnpm dev` runs turbo, which runs `portless` in each package. Portless detects the package manager and runs `pnpm run dev:app` through the proxy. When `portless` runs from a workspace root, it uses the existing Turbo integration to preserve task ordering when either `turbo.json` or `turbo.jsonc` is readable. Set `"turbo": false` in the root portless configuration to use direct spawning instead. ### package.json scripts You can still use portless directly in scripts: ```json { "scripts": { "dev": "portless run next dev" } } ``` The proxy auto-starts when you run an app. Or start it explicitly: `portless proxy start`. ### Multi-app setups with subdomains ```bash portless myapp next dev # https://myapp.localhost portless api.myapp pnpm start # https://api.myapp.localhost portless docs.myapp next dev # https://docs.myapp.localhost ``` By default, only explicitly registered subdomains are routed (strict mode). Start the proxy with `--wildcard` to allow any subdomain of a registered route to fall back to that app (e.g. `tenant1.myapp.localhost` routes to the `myapp` app). Exact matches always take priority over wildcards. When several registered routes are parents of the host, the most specific one wins (`admin.api.myapp.localhost` routes to `api.myapp`, not `myapp`). ### Git worktrees `portless run` automatically detects git worktrees. In a linked worktree, the branch name is prepended as a subdomain prefix so each worktree gets a unique URL: ```bash # Main worktree (no prefix) portless run next dev # -> https://myapp.localhost # Linked worktree on branch "fix-ui" portless run next dev # -> https://fix-ui.myapp.localhost ``` No config changes needed. Put `portless run` in `package.json` once and it works in all worktrees. ### Bypassing portless Set `PORTLESS=0` to run the command directly without the proxy: ```bash PORTLESS=0 pnpm dev # Bypasses proxy, uses default port ``` When a proxied command is stopped with Ctrl+C, portless waits for its process tree to exit. A second Ctrl+C forwards another interrupt, and remaining descendants are terminated after a short grace period. ## How It Works 1. `portless proxy start` starts an HTTPS reverse proxy on port 443 as a background daemon. Auto-elevates with sudo on macOS/Linux; falls back to port 1355 if sudo is unavailable. Use `--no-tls` for plain HTTP on port 80. Configurable with `-p` / `--port` or the `PORTLESS_PORT` env var. The proxy also auto-starts when you run an app. 2. `portless <name> <cmd>` assigns a random free port (4000-4999) via the `PORT` env var and registers the app with the proxy 3. The browser hits `https://<name>.localhost`; the proxy forwards to the app's assigned port Outside LAN mode, the proxy and its HTTP redirect listener bind only to the IPv4 and IPv6 loopback addresses, `127.0.0.1` and `::1`. They do not accept connections through LAN, VPN, or other network interfaces. `.localhost` domains resolve to `127.0.0.1` natively in Chrome, Firefox, and Edge. Safari relies on the system DNS resolver, which may not handle `.localhost` subdomains on all configurations. Run `portless hosts sync` to add entries to `/etc/hosts` if needed. Use `portless proxy start --tld localhost --tld test` to serve the same app names under multiple TLDs from one proxy. `PORTLESS_URL` uses the first configured TLD. When configured TLDs overlap (e.g. `example.com` and `dev.example.com`), hostnames are matched against the longest TLD first, regardless of configuration order. `PORTLESS_TLD` accepts the same comma separated list format, e.g. `PORTLESS_TLD=localhost,test`. TLDs can be multi-segment DNS names such as `dev.example.com`, so local URLs can mirror production structure (`myapp.dev.example.com`). Each label follows DNS rules: lowercase letters, digits, interior hyphens, 63 characters per label, 253 total. Strict OAuth providers that reject `.localhost` redirect URIs accept a real domain like `https://myapp.dev.example.com/api/auth/callback/google`. Most frameworks (Next.js, Express, Nuxt, etc.) respect the `PORT` env var automatically. For frameworks that ignore `PORT` (Vite, VitePlus, Astro, React Router, Angular, Expo, React Native), portless auto-injects the correct `--port` flag and, when needed, a matching `--host` CLI flag. Injection reaches through a package script whose command starts with the framework or a known runner (`"dev": "vite"`, `"dev": "bunx vite"`). Only the framework's server commands get the flags (`dev`, `serve`, `preview`, `start`, a bare `vite`, or `vite [root]`); a command that does not serve, such as `vite build`, `vite optimize`, `vp test` or `astro check`, rejects them and is left alone. Expo connection modes (`--localhost`, `--lan`, `--tunnel`) are preserved while the assigned port is still injected. A script portless cannot classify is left alone too: a flag before the subcommand on a CLI whose flag grammar it does not track (`vp --mode dev build`). Portless also leaves a script alone when appending flags to it would not work: a compound command (`&&`, `|`, `;`), a trailing `#` comment, its own `--` option terminator, an env prefix (`NODE_ENV=production vite`), delegation to another script (`"dev": "npm run dev:vite"`), or runner flags before the script name (`bun run --bun dev`). Those keep their own port, so set it in the script yourself. ### State directory Portless stores its state (routes, PID file, port file) in `~/.portless`. When the proxy runs under sudo, this remains the invoking user's home directory so unprivileged apps and the proxy share route registrations. Override with the `PORTLESS_STATE_DIR` environment variable. ### Environment variables | Variable | Description | | --------------------- | ------------------------------------------------------------------------------ | | `PORTLESS_PORT` | Override the default proxy port (default: 443 with HTTPS, 80 without) | | `PORTLESS_APP_PORT` | Use a fixed port for the app (skip auto-assignment) | | `PORTLESS_HTTPS` | HTTPS on by default; set to `0` to disable (same as `--no-tls`) | | `PORTLESS_LAN` | Set to `1` to always enable LAN mode (auto-detects LAN IP) | | `PORTLESS_LAN_IP` | Pin a specific LAN IP for LAN mode | | `PORTLESS_TLD` | Use one or more TLDs, single or multi-segment (e.g. localhost,dev.example.com) | | `PORTLESS_WILDCARD` | Set to `1` to allow unregistered subdomains to fall back to parent | | `PORTLESS_SYNC_HOSTS` | Set to `0` to disable auto-sync of /etc/hosts (on by default) | | `PORTLESS_TAILSCALE` | Set to `1` to share apps on your Tailscale network (same as `--tailscale`) | | `PORTLESS_FUNNEL` | Set to `1` to share apps publicly via Tailscale Funnel (same as `--funnel`) | | `PORTLESS_NGROK` | Set to `1` to share apps publicly via ngrok (same as `--ngrok`) | | `PORTLESS_STATE_DIR` | Override the state directory | | `PORTLESS=0` | Bypass the proxy, run the command directly | ### HTTP/2 + HTTPS HTTPS with HTTP/2 is enabled by default (faster page loads for dev servers with many files). WebSockets work over both HTTP/1.1 (Upgrade) and HTTP/2 (RFC 8441 extended CONNECT), so dev server HMR works through the proxy. First run generates a local CA and adds it to the system trust store. After that, no prompts and no browser warnings. ```bash portless proxy start --cert ./c.pem --key ./k.pem # Use custom certs portless proxy start --no-tls # Disable HTTPS (plain HTTP) portless trust # Add CA to trust store later ``` On Linux, `portless trust` supports Debian/Ubuntu, Arch, Fedora/RHEL/CentOS, and openSUSE (via `update-ca-certificates` or `update-ca-trust`). On Windows, it uses `certutil` to add the CA to the system trust store. On WSL, it updates both the Linux trust store and the Windows current-user Root store so Windows browsers trust portless HTTPS certificates. ### LAN mode ```bash portless proxy start --lan portless proxy start --lan --https portless proxy start --lan --ip 192.168.1.42 ``` `--lan` explicitly binds the proxy to the IPv4 and IPv6 unspecified addresses, `0.0.0.0` and `::`, and advertises `<name>.local` hostnames over mDNS so devices on the same Wi-Fi can reach your apps. Portless auto-detects your LAN IP and follows network changes automatically, but you can pin a specific address with `--ip <address>` or the `PORTLESS_LAN_IP` environment variable. Set `PORTLESS_LAN=1` to default to LAN mode every time the proxy starts. Portless remembers LAN mode via `proxy.lan`, so if you stop a LAN proxy and start again, it stays in LAN mode. All proxy settings (port, TLS, TLDs, LAN) are persisted and reused on auto-start unless overridden by explicit flags or env vars. Use `PORTLESS_LAN=0` for one start to switch back to `.localhost` mode. If a proxy is already running with different explicit LAN/TLS/TLD settings, portless warns and asks you to stop it first. LAN mode depends on the system mDNS helpers that portless launches: macOS includes `dns-sd`, while Linux uses `avahi-publish-address` from `avahi-utils` (install via `sudo apt install avahi-utils` or your distro’s tooling). - **Next.js**: add your `.local` hostnames to `allowedDevOrigins`: ```js // next.config.js module.exports = { allowedDevOrigins: ["myapp.local", "*.myapp.local"], }; ``` - **Expo / React Native**: portless always injects `--port`. React Native also gets `--host 127.0.0.1`. Expo gets `--host localhost` outside LAN mode, but in LAN mode portless leaves Metro on its default LAN host behavior instead of forcing `--host` or `HOST`. ### Tailscale sharing Share dev servers with teammates on your Tailscale network using `--tailscale`, or expose to the public internet with `--funnel`: ```bash portless myapp --tailscale next dev # -> https://myapp.localhost (local) # -> https://devbox.yourteam.ts.net (tailnet) portless myapp --funnel next dev # -> https://myapp.localhost (local) # -> https://devbox.yourteam.ts.net (public internet) ``` Tailscale HTTPS certificates must be enabled before `--tailscale` or `--funnel` can register HTTPS URLs. Funnel must also be enabled for the tailnet and node before `--funnel` can register the public URL. If either setting is missing, portless exits before starting the child process. Each `--tailscale` app is root-mounted on its own Tailscale HTTPS port (443, then 8443, 8444, etc.) so no framework `basePath` configuration is needed. Set `PORTLESS_TAILSCALE=1` to share every app by default. `portless list` shows both local and tailnet URLs. Tailscale serve registrations are cleaned up when the app exits. Requires `tailscale` CLI installed and connected, with Tailscale HTTPS certificates enabled. ### ngrok sharing Expose a dev server to the public internet with ngrok using `--ngrok`: ```bash portless myapp --ngrok next dev # -> https://myapp.localhost (local) # -> https://abc123.ngrok.app (public internet) ``` Set `PORTLESS_NGROK=1` to enable ngrok by default when portless runs an app. `portless list` shows both local and ngrok URLs. The ngrok tunnel is cleaned up when the app exits. Requires the `ngrok` CLI to be installed and authenticated with `ngrok config add-authtoken <token>`. ## OS startup service Use the service command when users want the proxy to start automatically after reboot: ```bash portless service install portless service install --lan portless service install --wildcard PORTLESS_STATE_DIR=~/.portless-lan PORTLESS_LAN=1 portless service install portless service status portless service uninstall ``` The service uses portless defaults unless install options or `PORTLESS_*` environment variables are provided: HTTPS on port 443 with `.localhost` names. `service install` accepts proxy options including `--port`, `--no-tls`, `--lan`, `--ip`, `--tld`, `--wildcard`, `--cert`, and `--key`. Use `--state-dir <path>` or `PORTLESS_STATE_DIR=<path>` to choose where service state and logs are written.
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기