关于 outport
Outport 是开发服务端口管理命令行工具,面向需要在项目和 Git worktree 间管理端口与本地域名的开发者。它将分配结果及计算出的 URL 写入 .env,并可配置 .test 域名代理。
主要功能
- 01确定性分配且避免端口冲突
- 02将端口和计算值写入 .env
- 03为服务分配 .test 主机名
- 04自动区分 Git worktree 实例
- 05提供状态与 JSON 输出
适用场景
- 01为项目中的开发服务分配端口
- 02在 monorepo 的多个 .env 文件中同步配置
- 03并行运行多个 worktree 实例
- 04查看服务端口、健康状态和 URL
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ steveclarke/ outport --skill outport安装后在 Claude Code 中输入/outport确认已加载。
详细介绍
Outport — 开发端口管理器
Outport 为开发服务分配确定且互不冲突的端口,分配 .test 主机名,并将所有内容写入 .env 文件。每个框架都会读取 .env——Rails、Nuxt、Django、Docker Compose 等均如此,因此端口和 URL 无需手动配置即可使用。
快速参考
# Project commands
outport init # Create outport.yml (interactive)
outport up # Allocate ports, assign hostnames, write .env
outport up --force # Clear and re-allocate all ports from scratch
outport down # Remove ports and clean .env files
# Inspect & diagnose
outport status # Show project status (ports, health, URLs)
outport status --computed # Include computed values
outport ports # Show ports with live process info (PID, memory, uptime)
outport ports --all # Full machine scan (Outport + non-Outport ports)
outport ports --down # Include ports with no running process
outport ports kill <svc> # Kill process by service name or port number
outport ports kill --orphans # Kill all orphaned dev processes
outport open # Open HTTP services in browser
outport open web # Open a specific service
outport open admin # Open a unique alias
outport open web:admin # Open a specific service alias
outport share # Tunnel HTTP services to public URLs
outport share web # Tunnel a specific service
outport qr # Show QR codes for mobile device access
outport qr --tunnel # Show QR codes with tunnel URLs
outport doctor # Check system health and project config
# System commands (machine-wide)
outport system start # Install DNS, CA, and start the daemon
outport system stop # Stop the daemon
outport system restart # Re-write plist and restart the daemon
outport system status # Show all registered projects
outport system status --check # Show with health checks (up/down)
outport system prune # Remove stale and duplicate registry entries
outport system uninstall # Remove DNS resolver, daemon, and CA
# Instance management
outport rename <old> <new> # Rename the current instance
outport promote # Promote the current instance to main
所有命令都支持 --json,用于输出机器可读的信息。
设置新项目
1. 创建 outport.yml
运行 outport init 进行交互式设置,或手动创建:
name: my-project
services:
web:
env_var: PORT
postgres:
env_var: DB_PORT
redis:
env_var: REDIS_PORT
2. 运行 outport up
分配确定的端口(根据项目名称和服务名称进行哈希计算),并将其写入 .env。相同的输入始终会生成相同的端口。
3. 配置项目从 .env 读取
大多数框架都能原生读取 .env,或只需少量设置即可读取:
- Docker Compose——自动读取
.env。在compose.yml中使用${DB_PORT:-5432} - Rails——使用
dotenv-railsgem,或在配置中引用环境变量:port: ENV.fetch("DB_PORT", 5432) - Nuxt——原生读取
.env。运行时配置值可通过NUXT_*环境变量覆盖 - Foreman——自动读取
.env - Overmind——不会自动加载
.env。在启动脚本中加载它:if [ -f .env ]; then set -a; source .env; set +a; fi
4. 提交 outport.yml,将 .env 加入 gitignore
outport.yml 是项目配置——提交它,以便工作树和团队成员都能继承配置。.env 包含分配的端口——将其加入 gitignore。每个检出副本都会有自己的文件。
.test 域名(DNS 代理)
运行一次 outport system start,即可为服务启用易记的 .test 主机名。完成设置后,https://myapp.test 会路由到你的应用,而不是 http://localhost:24920。
工作原理
outport system start 会安装三个组件(配置 DNS 和 CA 信任需要 sudo):
- DNS 解析器——配置操作系统,将
*.test查询发送到本地 15353 端口上的 DNS 服务器,该服务器会将所有*.test名称解析为127.0.0.1。在 macOS 上,此配置位于/etc/resolver/test;在 Linux 上,则是 systemd-resolved 的扩展配置。 - 反向代理——守护进程运行在 80 和 443 端口上,根据
Host标头将请求路由到相应的服务端口,并在运行outport up时自动更新。WebSocket 连接会透明代理。在 macOS 上由 launchd 管理,在 Linux 上由 systemd 管理。 - 本地 CA——生成用于 HTTPS 的证书颁发机构。80 端口上的 HTTP 请求会通过 307 重定向到 HTTPS。
outport system start # Install DNS + CA + daemon (one-time, prompts for sudo)
outport system stop # Stop the daemon
outport system restart # Re-write plist and restart the daemon
outport system uninstall # Remove everything — reverse of start
配置 .test 主机名
为服务添加 hostname,即可为其分配 .test URL:
name: myapp
services:
web:
env_var: PORT
hostname: myapp.test # → https://myapp.test
postgres:
env_var: DB_PORT # no hostname: port allocation only
主机名规则:
- 必须包含项目名称(例如,
myapp.test、app.myapp.test) - 非主实例会自动追加实例代码(请参阅多个实例)
Cookie 隔离
每个实例都有自己的 .test 主机名,因此浏览器 Cookie 和会话会自动隔离——无需使用无痕窗口:
myapp [main] web → 24920 http://myapp.test
myapp [bkrm] web → 28104 http://myapp-bkrm.test
配置参考
项目级字段
| 字段 | 必填 | 说明 |
|---|---|---|
name | 是 | 项目标识符。用于端口分配和主机名生成。 |
open | 否 | 服务名称列表,outport open 默认会打开这些服务。省略时,会打开所有配置了主机名的服务。 |
服务字段
| 字段 | 必填 | 说明 |
|---|---|---|
env_var | 是 | 写入 .env 的环境变量名称 |
hostname | 否 | 此服务的 .test 主机名(例如,myapp.test)。启用 HTTP。非主实例会追加实例代码。 |
aliases | 否 | 命名的备用主机名(标签 → 主机名的映射)。每个别名都会路由到同一端口。需要配置 hostname。 |
subdomains | 否 | 启用通配符子域名路由(*.myapp.test)。所有子域名都会路由到同一端口。需要配置 hostname。 |
preferred_port | 否 | 优先尝试的端口。如果端口已被占用,则回退到基于哈希的分配方式 |
env_file | 否 | 写入位置。可以是字符串或数组。默认为项目根目录下的 .env |
写入多个 .env 文件
对于 monorepo,通常需要将端口写入多个 .env 文件。为 env_file 使用
数组:
services:
rails:
env_var: RAILS_PORT
env_file:
- backend/.env
- frontend/.env # Frontend needs this to construct API URLs
计算值
应用不仅需要端口号,还需要 URL。计算值会根据服务映射计算环境变量,并将最终值写入
.env。
基本语法
computed:
API_URL:
value: "${rails.url:direct}/api/v1" # http://localhost:24920/api/v1
env_file: frontend/.env
CORS_ORIGINS:
value: "${web.url}" # http://myapp.test (or localhost:PORT)
env_file: backend/.env
${service_name.field}引用服务字段env_file为必填项(没有默认值,必须明确指定)- 计算值名称不得与服务的
env_var名称冲突
模板字段
| 模板 | 解析为 | 用途 |
|---|---|---|
${rails.port} | 24920 | 原始端口号 |
${rails.hostname} | myapp.test(如果未设置主机名,则为 localhost) | 仅主机名 |
${rails.url} | http://myapp.test | 面向浏览器的 URL(CORS、资源主机),通过代理路由 |
${rails.url:direct} | http://localhost:24920 | 绕过代理的服务间调用 |
${rails.env_var} | PORT | 服务的环境变量名称 |
${rails.alias.NAME} | app.myapp.test | 按标签指定的别名主机名 |
${rails.alias_url.NAME} | https://app.myapp.test | 按标签指定的别名 URL |
何时使用 url,何时使用 url:direct:
${service.url}— 用于浏览器发送的值(CORS 源、资源主机、 OAuth 重定向 URI)。配置后使用.test主机名。${service.url:direct}— 用于服务间调用(后端获取数据时使用的 API 基础 URL、Node 服务发起的 WebSocket 连接)。始终使用localhost,不经过代理。
独立变量和 bash 风格的参数展开
计算值支持独立变量和 bash 风格的参数展开,可用于感知实例的配置:
| 变量 | 主实例 | Worktree 实例(例如,bxcf) |
|---|---|---|
${project_name} | myapp | myapp |
${instance} | (空字符串) | bxcf |
${instance:-default} | default | bxcf |
${instance:+replacement} | (空字符串) | replacement |
${instance:+-${instance}} | (空字符串) | -bxcf |
常见模式 — Docker Compose 项目名称:
computed:
COMPOSE_PROJECT_NAME:
value: "${project_name}${instance:+-${instance}}"
env_file: .env
主实例的结果为 myapp,worktree 的结果为 myapp-bxcf,
从而让每个实例拥有彼此隔离的 Docker 容器。
按文件覆盖值
当同一个环境变量在不同文件中需要不同的值时(这在多个应用共用某种框架约定的
monorepo 中很常见),请对 env_file 条目使用对象语法:
computed:
NUXT_API_BASE_URL:
env_file:
- file: frontend/apps/main/.env
value: "${rails.url:direct}/api/v1"
- file: frontend/apps/portal/.env
value: "${rails.url:direct}/portal/api/v1"
你可以在同一个列表中混用普通字符串条目(使用顶层 value)和对象条目。
实际 monorepo 示例
使用两个 Nuxt 前端的 Rails 后端。后端需要前端 .test URL 作为 CORS 源。前端需要 Rails API URL 以便在服务端发起请求:
name: myapp
services:
rails:
env_var: RAILS_PORT
hostname: myapp.test
env_file: backend/.env
frontend_main:
env_var: MAIN_PORT
hostname: app.myapp.test
env_file:
- frontend/apps/main/.env
- backend/.env # Backend needs this for CORS
frontend_portal:
env_var: PORTAL_PORT
hostname: portal.myapp.test
env_file:
- frontend/apps/portal/.env
- backend/.env # Backend needs this for CORS
computed:
# Server-to-server API URLs (bypass proxy — use direct localhost)
NUXT_API_BASE_URL:
env_file:
- file: frontend/apps/main/.env
value: "${rails.url:direct}/api/v1"
- file: frontend/apps/portal/.env
value: "${rails.url:direct}/portal/api/v1"
# Backend CORS (browser-facing — use .test hostnames)
CORE_CORS_ORIGINS:
value: "${frontend_main.url},${frontend_portal.url}"
env_file: backend/.env
# Backend asset host (browser-facing)
SHRINE_ASSET_HOST:
value: "${rails.url}"
env_file: backend/.env
执行 outport up 后,每个服务都会获得正确的端口和 URL。
不会残留硬编码值。
框架环境变量约定
设置计算值时,了解框架如何将环境变量映射到配置至关重要:
| 框架 | 约定 | 示例 |
|---|---|---|
| Nuxt | NUXT_ 前缀映射到 runtimeConfig | NUXT_API_BASE_URL 覆盖 runtimeConfig.apiBaseUrl |
| Rails (AnyWayConfig) | PREFIX_ATTR 映射到配置类 | CORE_CORS_ORIGINS 覆盖 CoreConfig.cors_origins |
| Rails (Shrine) | 与 AnyWayConfig 使用相同模式 | SHRINE_ASSET_HOST 覆盖 ShrineConfig.asset_host |
| Django | 通常直接读取 os.environ | 根据 settings.py 的要求命名变量 |
| Docker Compose | 自动读取 .env | compose.yml 中的 ${DB_PORT:-5432} |
当计算值功能写入的环境变量符合这些框架约定时,它的作用最为显著—— 框架可以直接读取该值,无需更改配置代码。
.env 文件格式
Outport 会在每个 .env 文件末尾的围栏代码块中写入托管变量:
# Your own variables — Outport never touches these
SECRET_KEY=abc123
RAILS_ENV=development
# --- begin outport.dev ---
DB_PORT=21536
RAILS_PORT=24920
NUXT_API_BASE_URL=http://localhost:24920/api/v1
# --- end outport.dev ---
每次运行 outport up 时,围栏代码块都会替换为当前值。从 outport.yml 中移除的变量会从代码块中消失。代码块之外的所有内容都会保留。
多个实例
Outport 会自动检测 Git 工作树。每个工作树都会获得唯一的端口和自己的 .test 主机名,无需配置:
# Main checkout
$ outport up
my-app [main]
rails RAILS_PORT → 24920 http://my-app.test
web MAIN_PORT → 21349
# Worktree — different ports, different hostname, zero conflicts
$ cd ../my-app-feature && outport up
Registered as my-app-bkrm. Use 'outport rename bkrm <name>' to rename.
my-app [bkrm]
rails RAILS_PORT → 20192 http://my-app-bkrm.test
web MAIN_PORT → 21133
每个实例都会重新计算派生值,例如 CORS 源、API URL 和所有其他派生值,并自动使用该实例的端口和主机名。两个完整实例可同时运行,不会发生端口冲突或主机名冲突,也无需手动配置。
使用以下命令管理实例:
outport rename bkrm my-feature # Rename an instance
outport promote # Promote current instance to main
与设置脚本集成
在项目设置流程的早期运行 outport up,即在创建 .env 文件后、启动服务前。将其设为可选项,避免阻止未使用 Outport 的开发者:
# In bin/setup or similar
if command -v outport > /dev/null 2>&1; then
outport up
else
echo "Outport not found — using default ports"
echo "Install: brew install steveclarke/tap/outport"
fi
常见任务
与另一个项目发生端口冲突
在两个项目中都运行 outport up。Outport 的注册表可确保所有已注册项目之间不会发生冲突。
端口是旧分配中的过期值
运行 outport up --force 以清除并重新分配。
释放已不再使用的项目所占端口
运行 outport down,将项目从注册表中移除并释放所有端口。
向现有项目添加新服务
将其添加到 outport.yml 并运行 outport up。现有分配会保留,只有新服务会获得端口。
Agent 需要了解项目的 URL
运行 outport status --json,获取包含端口、运行状况和 URL 的结构化输出。
服务使用的端口与预期不同
运行 outport system status 查看所有分配。如果其他项目占用了你想要的端口,请先在该项目中运行 outport down,然后在你的项目中运行 outport up --force。
通过手机访问开发服务
运行 outport qr,显示包含每个 HTTP 服务的局域网 URL 的二维码。在同一 Wi-Fi 网络下用手机扫描即可打开应用。运行 outport share 时,可使用 outport qr --tunnel 获取包含公共隧道 URL 的二维码。也可在 outport.test 的仪表板中查看二维码。
与网络之外的人共享服务
运行 outport share,通过隧道将所有 HTTP 服务映射到公共 Cloudflare URL。需要 cloudflared(brew install cloudflared)。按 Ctrl+C 停止。
某些功能无法正常运行
运行 outport doctor 检查 DNS、守护进程、证书、注册表和项目配置。每项检查都会显示通过或失败,并提供修复建议。
.test 域名无法解析
运行 outport doctor 进行诊断。常见原因:守护进程未运行(outport system start),或缺少 DNS 解析器(outport system start)。
错误与功能请求
请通过 https://github.com/steveclarke/outport/issues 报告错误或请求新功能