关于 portless
portless 是一项开发技能,用稳定的命名 .localhost URL 取代本地 Web 应用变化的端口号。内容涵盖代理配置、Monorepo、Git worktree 和框架端口处理。
主要功能
- 01稳定的命名 .localhost URL
- 02HTTPS 反向代理
- 03发现 Monorepo 软件包
- 04为 Git worktree 分配独立 URL
- 05注入框架端口参数
适用场景
- 01同时运行多个本地应用以避免端口冲突
- 02按应用名称路由 Monorepo 服务
- 03为不同 Git worktree 保留独立 URL
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vercel-labs/ portless --skill portless安装后在 Claude Code 中输入/portless确认已加载。
详细介绍
Portless
用稳定的命名 .localhost URL 替代端口号。适用于人类和 Agent。
为什么使用 portless
- 端口冲突:两个项目默认使用同一端口时,会出现
EADDRINUSE - 记不住端口:哪个应用运行在 3001,哪个运行在 8080?
- 刷新后显示错误的应用:停止一个服务,再在同一端口启动另一个服务,旧标签页会显示错误的内容
- 单体仓库问题倍增:仓库中的每个服务都会让这些问题加剧
- Agent 测试了错误的端口:AI Agent 会猜错或硬编码错误的端口
- Cookie/存储冲突:
localhost上的 Cookie 会在应用间串用;端口变更后,localStorage 中的数据会丢失 - 配置中硬编码的端口:CORS 允许列表、OAuth 重定向和
.env文件会在端口变更后出错 - 与队友共享 URL:“那个应用用的是什么端口?”成了 Slack 上的问题
- 浏览器历史记录毫无用处:
localhost:3000的历史记录混杂着互不相关的项目
安装
全局安装(推荐),或将其作为项目开发依赖安装。不要使用 npx 或 pnpm dlx 临时执行。
# Global (available everywhere)
npm install -g portless
# Or per-project dev dependency
npm install -D portless
在项目内安装时,通过 package.json 脚本或 npx portless 调用(由于该软件包已在本地,npx 不会下载任何内容)。
快速入门
# 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
运行应用时,代理会自动启动。你也可以通过 portless proxy start 显式启动。自动启动会沿用最近一次代理运行时的配置(端口、TLS、TLD),因此重启或重新启动计算机时不会悄悄恢复默认值。显式设置的环境变量始终优先。
在非交互环境中(没有 TTY,或 CI=1),portless 会显示描述性错误并退出,而不会提示用户。turborepo 等任务运行器应预先启动代理。
集成模式
零配置(推荐)
portless 开箱即用。它会通过代理运行 package.json 中的 "dev" 脚本,并从软件包名称、git 根目录或目录推断应用名称:
portless # -> runs "dev" script, https://<project>.localhost
pnpm dev # -> works without portless, plain "next dev"
可以使用可选的 portless.json 覆盖默认设置(名称、脚本、端口):
{ "name": "myapp" }
portless # -> runs "dev" script, https://myapp.localhost
单体仓库
在仓库根目录放置一个 portless.json。Portless 会从 pnpm-workspace.yaml 或 package.json 中的 "workspaces" 字段(npm、yarn、bun)发现软件包:
{
"apps": {
"apps/web": { "name": "myapp" },
"apps/api": { "name": "api.myapp" }
}
}
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"
apps 映射可选,仅用于覆盖名称。未列出的软件包会自动发现,并使用推断出的名称。
没有 apps 映射时,主机名遵循 <package>.<project>.localhost 格式。项目名称取最常见的 npm 作用域(例如,@myorg/web 和 @myorg/api 会生成 myorg),如果没有则使用工作区根目录名称。如果某个软件包的简短名称与项目名称相同,则使用不带前缀的 <project>.localhost。
Turborepo
对于 turborepo 项目,将 portless 用作 dev 脚本,并将实际命令放在单独的脚本中:
{
"scripts": { "dev": "portless", "dev:app": "next dev" },
"portless": { "name": "myapp", "script": "dev:app" }
}
pnpm dev 会运行 turbo,而 turbo 会在每个软件包中运行 portless。Portless 会检测软件包管理器,并通过代理运行 pnpm run dev:app。
当 portless 从工作区根目录运行时,只要 turbo.json 或 turbo.jsonc 可读,它就会使用现有的 Turbo 集成来保留任务顺序。在根目录的 portless 配置中设置 "turbo": false,即可改用直接启动。
package.json 脚本
你仍然可以在脚本中直接使用 portless:
{
"scripts": {
"dev": "portless run next dev"
}
}
运行应用时,代理会自动启动。也可以显式启动:portless proxy start。
使用子域名的多应用配置
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
默认情况下,只会路由显式注册的子域名(严格模式)。启动代理时加上 --wildcard,即可允许任何已注册路由的子域名回退到对应应用(例如,tenant1.myapp.localhost 会路由到 myapp 应用)。精确匹配始终优先于通配符。如果主机匹配多个已注册路由的父级,则使用最具体的路由(admin.api.myapp.localhost 会路由到 api.myapp,而不是 myapp)。
Git worktree
portless run 会自动检测 Git worktree。在关联的 worktree 中,分支名称会作为子域名前缀,因此每个 worktree 都有唯一的 URL:
# 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
无需更改配置。在 package.json 中添加一次 portless run,即可在所有 worktree 中使用。
绕过 portless
设置 PORTLESS=0,即可绕过代理直接运行命令:
PORTLESS=0 pnpm dev # Bypasses proxy, uses default port
使用 Ctrl+C 停止通过代理运行的命令时,portless 会等待其进程树退出。再次按下 Ctrl+C 会转发另一个中断信号,剩余的子进程会在短暂的宽限期后终止。
工作原理
portless proxy start会在端口 443 上启动 HTTPS 反向代理,并将其作为后台守护进程运行。在 macOS/Linux 上会自动提权使用 sudo;如果 sudo 不可用,则回退到端口 1355。使用--no-tls可在端口 80 上启用纯 HTTP。可通过-p/--port或PORTLESS_PORT环境变量进行配置。运行应用时,代理也会自动启动。portless <name> <cmd>会通过PORT环境变量分配一个随机可用端口(4000-4999),并向代理注册应用。- 浏览器访问
https://<name>.localhost;代理会将请求转发到分配给应用的端口。
在 LAN 模式之外,代理及其 HTTP 重定向监听器仅绑定到 IPv4 和 IPv6 回环地址 127.0.0.1 和 ::1。它们不会接受来自 LAN、VPN 或其他网络接口的连接。
当 portless 自身返回错误时(未知主机返回 404、请求目标错误返回 421、应用无法访问时返回 502、请求循环时返回 508),响应正文取决于 Accept 标头。浏览器(text/html)会收到 HTML 页面。curl、fetch 和其他客户端会收到 JSON,例如 {"error":"bad_gateway","upstream":"myapp.localhost","message":"..."},其中 error 的值为 not_found、misdirected_request、bad_gateway 或 loop_detected。请解析该 JSON,而不是抓取 HTML。应用自身的响应会原样转发。对于 portless TLD 之外的主机名,例如 DNS 屏蔽列表将其指向 127.0.0.1 的网站,会返回 421 Misdirected Request,而不是应用列表。
.localhost 域名在 Chrome、Firefox 和 Edge 中会原生解析到 127.0.0.1。Safari 依赖系统 DNS 解析器,某些配置下可能无法处理 .localhost 子域名。如有需要,运行 portless hosts sync 将条目添加到 /etc/hosts。
使用 portless proxy start --tld localhost --tld test,即可通过一个代理在多个 TLD 下提供相同的应用名称。PORTLESS_URL 使用第一个已配置的 TLD。当配置的 TLD 存在重叠(例如 example.com 和 dev.example.com)时,无论配置顺序如何,都会优先匹配最长的 TLD。PORTLESS_TLD 接受相同的逗号分隔列表格式,例如 PORTLESS_TLD=localhost,test。
TLD 可以是多段 DNS 名称,例如 dev.example.com,因此本地 URL 可以映射生产环境的结构(myapp.dev.example.com)。每个标签都必须符合 DNS 规则:小写字母、数字、内部连字符;每个标签最多 63 个字符,总长度最多 253 个字符。拒绝 .localhost 重定向 URI 的严格 OAuth 提供商,可以接受 https://myapp.dev.example.com/api/auth/callback/google 这样的真实域名。
大多数框架(Next.js、Express、Nuxt 等)都会自动遵循 PORT 环境变量。对于忽略 PORT 的框架(Vite、VitePlus、VitePress、Astro、React Router、Angular、Expo、React Native、Wrangler),portless 会自动注入正确的 --port 标志,并在需要时注入匹配的 --host CLI 标志(Wrangler 使用 --ip)。如果包脚本中的命令以框架或已知运行器开头("dev": "vite"、"dev": "bunx vite"),注入会继续向下传递。只有框架的服务器命令会获得这些标志(dev、serve、preview、start、单独的 vite 或 vite [root]);不提供服务的命令(如 vite build、vite optimize、vp test 或 astro check)不接受这些标志,因此不会修改。如果 Expo 连接模式使用 --localhost、--lan 或 --tunnel,这些模式会保留,同时仍会注入分配的端口。portless 无法分类的脚本也不会修改,例如,在 portless 不追踪其标志语法的 CLI 中,子命令之前带有标志的命令(vp --mode dev build)。如果向脚本追加标志不起作用,portless 也会保持脚本不变:复合命令(&&、|、;)、末尾带有 # 注释、自带 -- 选项终止符、带有环境变量前缀(NODE_ENV=production vite)、委托给另一个脚本("dev": "npm run dev:vite"),或运行器标志位于脚本名称之前(bun run --bun dev)。这些脚本会使用自身的端口,因此请在脚本中自行设置端口。
状态目录
Portless 将其状态(路由、PID 文件、端口文件)存储在 ~/.portless 中。代理在 sudo 下运行时,状态目录仍为调用者的主目录,因此非特权应用和代理可以共享路由注册信息。可通过环境变量 PORTLESS_STATE_DIR 覆盖此设置。
环境变量
| 变量 | 描述 |
|---|---|
PORTLESS_PORT | 覆盖代理的默认端口(默认值:启用 HTTPS 时为 443,否则为 80) |
PORTLESS_APP_PORT | 为应用指定固定端口(跳过自动分配) |
PORTLESS_HTTPS | 默认启用 HTTPS;设为 0 可禁用(等同于 --no-tls) |
PORTLESS_LAN | 设为 1 可始终启用 LAN 模式(自动检测 LAN IP) |
PORTLESS_LAN_IP | 为 LAN 模式指定固定的 LAN IP |
PORTLESS_TLD | 使用一个或多个 TLD,可为单段或多段(例如 localhost、dev.example.com) |
PORTLESS_WILDCARD | 设为 1 可允许未注册的子域名回退到父域名 |
PORTLESS_SYNC_HOSTS | 设为 0 可禁用 /etc/hosts 的自动同步(默认启用) |
PORTLESS_TAILSCALE | 设为 1 可在 Tailscale 网络上共享应用(等同于 --tailscale) |
PORTLESS_FUNNEL | 设为 1 可通过 Tailscale Funnel 公开共享应用(等同于 --funnel) |
PORTLESS_NGROK | 设为 1 可通过 ngrok 公开共享应用(等同于 --ngrok) |
PORTLESS_CLOUDFLARE | 设为 1 可通过 Cloudflare 公开共享应用(等同于 --cloudflare) |
PORTLESS_STATE_DIR | 覆盖状态目录 |
PORTLESS=0 | 绕过代理,直接运行命令 |
HTTP/2 + HTTPS
默认启用带 HTTP/2 的 HTTPS(可加快包含许多文件的开发服务器页面加载速度)。WebSocket 可通过 HTTP/1.1(Upgrade)和 HTTP/2(RFC 8441 扩展 CONNECT)运行,因此开发服务器的 HMR 可通过代理工作。首次运行时会生成本地 CA,并将其添加到系统信任存储区。此后不会再出现提示或浏览器警告。
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
在 Linux 上,portless trust 支持 Debian/Ubuntu、Arch、Fedora/RHEL/CentOS 和 openSUSE(通过 update-ca-certificates 或 update-ca-trust)。在 Windows 上,它使用 certutil 将 CA 添加到系统信任存储区。在 WSL 上,它会同时更新 Linux 信任存储区和 Windows 当前用户的 Root 存储区,以便 Windows 浏览器信任 portless HTTPS 证书。WSL2 主机上的 Linux 容器仅使用 Linux 信任存储区。
LAN 模式
portless proxy start --lan
portless proxy start --lan --https
portless proxy start --lan --ip 192.168.1.42
--lan 会显式将代理绑定到 IPv4 和 IPv6 未指定地址 0.0.0.0 和 ::,并通过 mDNS 广播 <name>.local 主机名,让同一 Wi-Fi 网络中的设备能够访问你的应用。Portless 会自动检测 LAN IP,并自动跟随网络变化;你也可以使用 --ip <address> 或环境变量 PORTLESS_LAN_IP 指定一个地址。设置 PORTLESS_LAN=1,即可让代理每次启动时默认进入 LAN 模式。
Portless 会通过 proxy.lan 记住 LAN 模式,因此停止 LAN 代理后再次启动时,它仍会保持 LAN 模式。除非显式标志或环境变量另有指定,所有代理设置(端口、TLS、TLD、LAN)都会持久保存,并在自动启动时沿用。使用 PORTLESS_LAN=0 可在某次启动时切回 .localhost 模式。如果代理已在运行,且其显式 LAN/TLS/TLD 设置不同,portless 会发出警告并要求你先停止代理。
LAN 模式依赖 portless 启动的系统 mDNS 辅助工具:macOS 内置 dns-sd;Linux 使用 avahi-utils 中的 avahi-publish-address(可通过 sudo apt install avahi-utils 或发行版提供的工具安装)。
-
Next.js:将你的
.local主机名添加到allowedDevOrigins:// next.config.js module.exports = { allowedDevOrigins: ["myapp.local", "*.myapp.local"], }; -
Expo / React Native:portless 始终会注入
--port。React Native 还会获得--host 127.0.0.1。在 LAN 模式之外,Expo 会获得--host localhost;而在 LAN 模式下,portless 会让 Metro 保持默认的 LAN 主机行为,不强制设置--host或HOST。
Tailscale 共享
使用 --tailscale 与 Tailscale 网络中的队友共享开发服务器,或使用 --funnel 将其公开到互联网:
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 证书,--tailscale 或 --funnel 才能注册 HTTPS URL。必须先为 tailnet 和节点启用 Funnel,--funnel 才能注册公共 URL。如果缺少任一设置,portless 会在启动子进程前退出。
每个 --tailscale 应用都会挂载在各自的 Tailscale HTTPS 端口根路径上(443、然后是 8443、8444 等),因此无需配置框架的 basePath。设置 PORTLESS_TAILSCALE=1,即可默认共享每个应用。portless list 会显示本地和 tailnet URL。应用退出时,会清理 Tailscale serve 注册项。需要安装并连接 tailscale CLI,且启用 Tailscale HTTPS 证书。
ngrok 共享
使用 --ngrok 将开发服务器公开到互联网:
portless myapp --ngrok next dev
# -> https://myapp.localhost (local)
# -> https://abc123.ngrok.app (public internet)
设置 PORTLESS_NGROK=1,即可在 portless 运行应用时默认启用 ngrok。portless list 会显示本地和 ngrok URL。应用退出时,会清理 ngrok 隧道。需要安装 ngrok CLI,并通过 ngrok config add-authtoken <token> 完成身份验证。
Cloudflare 共享
portless myapp --cloudflare next dev
# -> https://<random>.trycloudflare.com (public internet)
--cloudflare 会开启 Cloudflare 快速隧道;无需 Cloudflare 账户。设置 PORTLESS_CLOUDFLARE=1,即可默认启用。应用会获得 PORTLESS_CLOUDFLARE_URL,portless list 会显示一行 cloudflare:,应用退出时隧道会停止。它可与 --ngrok、--tailscale 和 --funnel 组合使用。快速隧道不支持 Server-Sent Events,最多允许 200 个进行中的请求,且每次运行都会获得一个新 URL。需要 PATH 中有 cloudflared CLI(https://developers.cloudflare.com/tunnel/downloads/)。
OS 启动服务
用户希望代理在重启后自动启动时,请使用 service 命令:
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
除非提供安装选项或 PORTLESS_* 环境变量,否则该服务使用 portless 默认设置:通过 HTTPS 在端口 443 上运行,并使用 .localhost 名称。service install 接受代理选项,包括 --port、--no-tls、--lan、--ip、--tld、--wildcard、--cert 和 --key。使用 --state-dir <path> 或 PORTLESS_STATE_DIR=<path> 来选择服务状态和日志的写入位置。
所选服务配置会写入 launchd、systemd 或 Task Scheduler,并在重启后继续使用。portless service status 会报告已安装的端口、HTTPS 模式、TLD、LAN 模式、通配符模式和状态目录。macOS 和 Linux 会安装由 root 所有的服务,以便在启动时绑定端口 443。Windows 会安装一个以 SYSTEM 身份运行的 Task Scheduler 启动任务。安装和移除可能需要管理员权限。portless clean 会自动移除该服务。
CLI 参考
| 命令 | 描述 |
|---|---|
portless | 通过代理运行开发脚本 |
portless | 从 monorepo 根目录运行:运行所有工作区包 |
portless --script <name> | 运行指定的 package.json 脚本(默认:dev) |
portless run [cmd] [args...] | 根据项目推断名称,通过代理运行(自动启动) |
portless run --name <name> <cmd> | 覆盖推断出的基础名称(仍会应用 worktree 前缀) |
portless <name> <cmd> [args...] | 在 https://<name>.localhost 运行应用(自动启动代理) |
portless get <name> | 输出服务的 URL(用于跨服务连接) |
portless get <name> --no-worktree | 输出不带 worktree 前缀的 URL |
portless list | 显示活动路由和运行时长 |
portless doctor | 检查代理、路由、DNS、CA 信任和 LAN 前置条件 |
portless trust | 将本地 CA 添加到系统信任存储(用于 HTTPS) |
portless clean | 移除状态、CA 信任条目和 /etc/hosts 区块 |
portless prune | 终止孤立的开发服务器进程组;run 启动时也会执行此操作 |
portless prune --force | 使用 SIGKILL 而非 SIGTERM 终止孤立进程 |
portless proxy start | 将 HTTPS 代理作为守护进程启动(端口 443,自动提升权限) |
portless proxy start --no-tls | 不启用 HTTPS 启动(在端口 80 上使用普通 HTTP) |
portless proxy start --lan | 以 LAN 模式启动(mDNS .local,自动跟随 LAN IP 变化) |
portless proxy start -p <number> | 在自定义端口启动代理 |
portless proxy start --tld test | 使用 .test 替代 .localhost |
portless proxy start --tld localhost --tld test | 通过同一个代理提供这两个 TLD |
portless proxy start --tld dev.example.com | 使用多段 TLD,提供与生产环境一致的 URL |
portless proxy start --foreground | 在前台启动代理(用于调试) |
portless proxy start --wildcard | 允许未注册的子域名回退到父路由 |
portless proxy stop | 停止代理 |
portless service install | 在操作系统启动时启动 HTTPS 代理 |
portless service install --lan | 以 LAN 模式启动服务 |
portless service install --wildcard | 在启动服务中持久启用通配符路由 |
portless service status | 显示服务和代理状态 |
portless service uninstall | 移除启动服务 |
portless alias <name> <port> | 注册静态路由(例如,用于 Docker 容器) |
portless alias <name> <port> --force | 覆盖现有路由 |
portless alias --remove <name> | 移除静态路由 |
portless hosts sync | 将路由与 /etc/hosts 同步(修复 Safari 问题) |
portless hosts clean | 从 /etc/hosts 移除 portless 条目 |
portless <name> --app-port <n> <cmd> | 为应用使用固定端口,而非自动分配 |
portless <name> --tailscale <cmd> | 在你的 Tailscale 网络(tailnet)上共享应用 |
portless <name> --funnel <cmd> | 通过 Tailscale Funnel 公开共享应用 |
portless <name> --ngrok <cmd> | 通过 ngrok 公开共享应用 |
portless <name> --cloudflare <cmd> | 通过 Cloudflare 快速隧道公开共享应用 |
portless <name> --force <cmd> | 终止现有进程并接管其路由 |
portless --name <name> <cmd> | 强制将 <name> 设为应用名称(绕过子命令分派) |
portless <name> -- <cmd> [args...] | 停止解析标志;-- 之后的所有内容都会传递给子进程 |
portless --help / -h | 显示帮助 |
portless run --help | 显示子命令帮助(另有:alias、hosts、clean) |
portless --version / -v | 显示版本 |
保留名称: run、get、alias、hosts、list、doctor、trust、clean、prune、proxy、service 和 mcp 都是子命令,不能直接用作应用名称。使用 portless run <cmd> 推断名称,或使用 portless --name <name> <cmd> 强制指定名称,包括保留名称。 |
MCP 服务
portless mcp [--tools <profiles>] 会启动一个 stdio MCP 服务。默认使用 core,提供路由列表、URL 查询、静态别名管理和配置发现功能。按需添加以逗号分隔的 proxy、diagnostics 或 all 配置。default 是 core 的别名,full 是 all 的别名。tools_profiles 工具会列出可用配置。共享工具参数为 cwd、extraArgs 和 timeoutMs;参数无效(未知或类型错误)以及调用未启用的工具时,都会返回一个 isError: true 的工具结果,说明如何修正,以便模型调整调用;未知参数会列出该工具自身的参数。MCP 不会运行开发服务器;请在 shell 中使用 portless run <cmd> 启动。
{ "mcpServers": { "portless": { "command": "portless", "args": ["mcp"] } } }
portless.json
可选配置文件。Portless 会在当前目录中查找此文件。
| 字段 | 类型 | 默认值 | 描述 |
|---|---|---|---|
name | 字符串 | 从 package.json 推断 | 基础应用名称(仍会应用 worktree 前缀) |
script | 字符串 | "dev" | 要运行的 package.json 脚本名称 |
appPort | 数字 | 自动分配 | 子进程使用的固定端口 |
proxy | 布尔值 | 自动检测 | 是否通过代理路由(任务模式下为 false) |
apps | 对象 | 工作区软件包的覆盖项,以相对路径为键 | |
turbo | 布尔值 | true | 设为 false 可直接启动,而不使用 turborepo |
每个 apps 条目都采用相同的结构(name、script、appPort、proxy)。如果存在 apps,顶层字段仅在单应用模式下生效。
package.json "portless" 键
除了使用单独的 portless.json,你也可以在 package.json 中添加 "portless" 键。字符串值是设置名称的简写:
{ "portless": "myapp" }
对象支持所有单应用字段(name、script、appPort、proxy):
{ "portless": { "name": "myapp", "script": "dev:app" } }
优先级(越接近应用,优先级越高):CLI 标志 > package.json "portless" 键 > portless.json 应用条目 > 默认值。
故障排除
运行诊断
当本地路由或 HTTPS 行为异常时,请先使用 portless doctor。它只读,并会检查 Node.js、状态目录权限、代理运行状态、路由条目、主机名解析、本地 CA 信任以及 LAN 模式的前置条件。
代理未运行
使用 portless <name> <cmd> 运行应用时,代理会自动启动。如果代理未能启动(例如端口冲突),请手动启动:
portless proxy start
端口已被占用
另一个进程正在使用代理端口。你可以先停止该进程,或使用其他端口:
portless proxy start -p 8080
框架未遵循 PORT
对于忽略 PORT 环境变量的框架,Portless 会自动注入正确的 --port 标志;必要时还会注入匹配的 --host 标志:Vite、VitePlus(vp)、VitePress、Astro、React Router、Angular、Expo、React Native 和 Wrangler(它使用 --ip 而不是 --host)。SvelteKit 内部使用 Vite,因此会自动处理。只有当包脚本的命令以框架名称或已知运行器开头,且运行的是框架的服务命令(dev、serve、preview、start 或单独的 vite)时,才会注入标志。vite build、vite optimize、vp test 等其他非服务命令不接受这些标志,因此不会修改;Portless 无法识别的调用(vp --mode dev build)也不会修改。对于复合命令(&&、|、;)、尾随的 # 注释、自带的 -- 选项终止符、环境变量前缀(NODE_ENV=production vite)、委托给其他脚本的命令,以及脚本名称前带有运行器标志的命令(bun run --bun dev),同样会跳过注入——这些情况下应用会继续使用自己的端口,并返回 502,因此请在脚本中自行设置端口。
对于其他不会读取 PORT 的框架,请手动传入端口:
- Webpack Dev Server:使用
--port $PORT - 自定义服务:读取
process.env.PORT并在该端口上监听
权限错误
默认端口(HTTP 为 80,HTTPS 为 443)在 macOS 和 Linux 上需要 sudo。必要时,Portless 会自动通过 sudo 提升权限。如果无法使用 sudo,则会回退到端口 1355(无需 sudo)。Windows 上无需提升权限。
portless proxy start --https # Auto-elevates with sudo for port 443
portless proxy start -p 1355 --https # No sudo needed (URLs include :1355)
portless proxy stop # Stop (use sudo if started with sudo)
Safari 找不到 .localhost URL
Safari 依赖系统 DNS 解析器来解析 .localhost 子域名,而某些 macOS 配置可能无法解析这些域名。Chrome、Firefox 和 Edge 内置了相应处理。
修复方法:
portless hosts sync # Reconcile current routes with /etc/hosts
portless hosts clean # Remove entries later
默认情况下,会自动同步 /etc/hosts 中的路由主机名。设置 PORTLESS_SYNC_HOSTS=0 可禁用此功能。如果路由主机名无法解析,注册该主机名的命令会发出警告,并提示你运行 portless hosts sync。
手动同步会将 Portless 管理的条目与当前路由同步;没有路由时会删除过期条目。写入前必须成功读取 hosts 文件,并会验证每次写入。读取或验证失败时,会按照常规同步错误流程处理。
使用 --https 时浏览器显示证书警告
本地 CA 可能尚未受信任。运行:
portless trust
这会将 Portless 本地 CA 添加到系统信任存储中。完成后,重启浏览器。
从机器中移除 Portless
portless clean
必要时停止代理,从信任存储中移除 Portless CA(如果是由 Portless 添加的),删除状态目录下的已知文件,并移除 Portless 在 /etc/hosts 中创建的区块。在 macOS/Linux 上可能需要 sudo。如果移除信任存储中的证书失败,Portless 会保留其 CA 证书和密钥,以便稍后安全地重试 portless clean。
代理循环(508 Loop Detected)
如果开发服务将请求代理到另一个 Portless 应用(例如,Vite 将 /api 代理到 api.myapp.localhost),代理必须重写 Host 标头。否则,Portless 会将请求路由回原始应用,从而造成无限循环。
修复方法:在代理配置(Vite、webpack-dev-server 等)中设置 changeOrigin: true:
// vite.config.ts
proxy: {
"/api": {
target: "https://api.myapp.localhost",
changeOrigin: true,
ws: true,
},
}
Portless 会在子进程中自动设置 NODE_EXTRA_CA_CERTS,使 Node.js 信任 Portless CA。如果你在 Portless 外部运行单独的 Node.js 进程,请手动指定 CA:NODE_EXTRA_CA_CERTS=~/.portless/ca.pem。或者,使用 --no-tls 来启用纯 HTTP。
Tailscale 无法正常工作
如果 --tailscale 或 --funnel 失败:
tailscale status # Check if connected
tailscale up # Connect to your tailnet
需要安装 Tailscale CLI(https://tailscale.com/download),并确保它位于 PATH 中。
ngrok 无法正常工作
如果 --ngrok 失败:
ngrok version # Check if installed
ngrok config add-authtoken <token> # Configure authentication
需要安装 ngrok CLI(https://ngrok.com/download),并确保它位于 PATH 中。
要求
- Node.js 24+
- macOS、Linux 或 Windows
openssl(用于生成--https证书;macOS 和大多数 Linux 发行版均自带;在 Windows 上,可通过winget install -e --id ShiningLight.OpenSSL.Dev安装,或使用 Git for Windows 附带的副本)tailscaleCLI(可选,用于--tailscale和--funnel)ngrokCLI(可选,用于--ngrok)cloudflaredCLI(可选,用于--cloudflare)