家里的 NAS 跑了 Jellyfin、相册、下载工具和各种管理页面,但家庭宽带没有稳定公网入口。常见处理办法是手写 SSH 反向隧道,再配一层 Nginx 或 Caddy。服务少的时候还能维护,服务一多,端口、域名、隧道状态和配置文件很快就会乱。
PortLoom 把这套流程做成了一个 Web 控制台。公网 VPS 负责接收 HTTPS 请求,NAS 上的 Agent 主动连接 VPS,再把流量送回本地服务。NAS 不需要开放入站端口,也不用在路由器上逐个做端口转发。
本文基于当前的 v0.4 系列(官方新手模板固定使用经过验证的 0.4.1 镜像)。下面的域名和 IP 都是示例,部署时换成自己的值。
PortLoom 的工作方式
PortLoom 需要两台能运行 Docker Compose 的 Linux 主机:
| 位置 | 安装内容 | 用途 |
|---|---|---|
| 公网 VPS | Server、受管 sshd | WebUI、内置 HTTPS 入口、路由网关和隧道入口 |
| NAS 或内网服务器 | Agent | 主动连接 VPS,并把请求转发给本地服务 |
v0.4 起 HTTPS 证书由 Server 内置的 edge 直接申请和续期(ACME),不再需要单独的 Caddy 容器。
一次访问大致会经过这条链路:
浏览器 │ HTTPS ▼公网 VPS PortLoom Server(内置 HTTPS edge → Gateway) │ │ SSH 反向隧道 ▼内网 NAS PortLoom Agent → Jellyfin / 相册 / 管理页面Agent 只向外连接 VPS 的 HTTPS 和 SSH 端口。公网请求沿已经建立的加密隧道返回 NAS,因此家庭网络不需要公网 IPv4,也不需要把 NAS 的服务端口直接暴露出去。
PortLoom 以 HTTP/HTTPS 域名路由为主。v0.4 起也支持发布 TCP/UDP 端口路由(在路由里选择对应协议并填写 Public port,同时在 VPS 放行该端口),可以覆盖数据库、游戏服务器这类非 Web 服务。
开始前要准备什么
1. 公网 VPS
VPS 需要:
- 一个稳定的公网 IP
- Linux 系统
- Docker Engine
- Docker Compose v2
- TCP
80、443和2222可以从公网访问
80/443 由 Server 内置的 HTTPS edge 处理(ACME HTTP-01 签发要求公网 80 可达,且本机 80/443 未被其他程序占用),2222 是 PortLoom 专用 SSH 隧道端口。
2. NAS 或内网服务器
NAS 需要能运行 Docker Compose,并且可以主动访问 VPS 的 443 和 2222 端口。NAS 端不需要放行公网入站端口。
3. 域名和 DNS
假设:
- VPS 公网 IP:
203.0.113.10 - PortLoom 管理域名:
portloom.example.com(管理域名没有命名要求,根域名或任意子域名都可以,不要求portloom.前缀) - 准备发布的 Jellyfin 域名:
jellyfin.example.com
添加两条 DNS 记录:
portloom.example.com A 203.0.113.10*.example.com A 203.0.113.10通配符记录不是强制的,也可以给每个服务单独添加 A 记录。使用 Cloudflare 时,第一次部署建议先设为“仅 DNS”(灰云),避免 SSH 端口和证书签发被代理状态干扰。
确认解析结果:
nslookup portloom.example.comnslookup jellyfin.example.com两次查询都应该返回 VPS 的公网 IP。
在 VPS 安装 PortLoom Server
v0.4 官方推荐的新手路径是 Compose 模板:下载两个文件、修改两个值、启动项目,不需要先运行任何安装脚本,配置全程可见。
方式一:Compose 模板(推荐)
在 VPS 上创建一个专用目录(例如 portloom/),从官方文档下载 compose.yml 和环境变量模板,把 compose.env.example 重命名为 .env,放在同一目录:
portloom/├── compose.yml└── .env打开 .env,只需要填两个必填值:
TM_PUBLIC_HOST:你的管理域名,例如portloom.example.com(不要带https://、端口或路径)TM_ADMIN_TOKEN:终端运行openssl rand -hex 32,把生成的 64 位十六进制串粘贴进去,它就是 WebUI 的登录凭证
两个值留空时 Compose 会拒绝启动,避免带着占位凭证误上线。
WARNING
.env里的管理员 Token 相当于控制台密码。不要提交到 Git、放进截图或发给别人。建议同时保存到密码管理器。
然后在该目录执行:
chmod 0711 .chmod 0600 .envdocker compose config --quietdocker compose pulldocker compose up -ddocker compose ps -a正常会看到:
portloom-server:运行中(WebUI、API、路由网关和内置 HTTPS edge)portloom-sshd:运行中且健康(专门用于反向隧道的受限 SSH 服务)state-init:成功退出——它只负责首次初始化数据目录权限,退出不是故障
群晖/QNAP 这类带 Compose 图形界面的系统,也可以直接在界面里选择这个目录新建项目启动,效果相同。
方式二:安全安装脚本
如果希望自动生成随机凭证、固定不可变镜像并带失败回滚,可以用安装脚本(先下载查看内容再执行):
curl -fsSLo install-server.sh https://docs.look4i.com/install-server.shless install-server.shchmod 0700 install-server.sh./install-server.sh \ --domain portloom.example.com \ --version 0.4.1两种方式运行的是相同的 Server 和 sshd 镜像。
打开 WebUI
容器正常后,在浏览器打开:
https://portloom.example.com输入管理员 Token 登录(Compose 方式用 .env 里的 TM_ADMIN_TOKEN,脚本方式用终端显示的随机 Token)。第一次访问可能需要几十秒完成证书申请;页面能打开后,可以顺手验证健康端点:
curl --fail --head https://portloom.example.com/healthz在 WebUI 添加 NAS
登录后进入 Add Agent 页面,填写:
| 字段 | 示例 |
|---|---|
| Agent name | home-nas |
| Server URL | https://portloom.example.com |
| Public Server host | portloom.example.com |
| SSH tunnel port | 2222 |
点击 Generate command。WebUI 会生成一条完整安装命令,其中包含一次性注册令牌和已经核验的 SSH 主机公钥。
这条命令不要自己拆开修改,也不要发到公开的地方。没执行过的安装命令可以在令牌列表里删除或撤销,已完成注册的 Agent 不受影响。
在 NAS 安装 Agent
SSH 登录 NAS,把 WebUI 生成的完整命令原样执行。它会下载 install-agent.sh,并安装与 Server 相同版本的 Agent。
安装过程会自动完成这些事情:
- 生成独立 Ed25519 密钥
- 写入 Server 的 SSH 主机公钥
- 使用一次性令牌注册 Agent
- 上传 Agent 公钥并更新受限授权文件
- 建立反向隧道
- 注册成功后,从环境文件中删除一次性令牌
默认安装目录是:
~/.portloom/agent查看 Agent 状态:
cd ~/.portloom/agentdocker compose --env-file .env -f compose.yml psdocker compose --env-file .env -f compose.yml logs --tail=100 agent回到 WebUI 的“客户端”页面,应该能看到 home-nas,并且心跳时间会持续更新。
发布第一个服务
下面以 Jellyfin 为例。假设 Jellyfin 在 NAS 上监听 8096。
进入 Routes → Add route,填写:
| 字段 | 示例 |
|---|---|
| Name | Jellyfin |
| Client | home-nas |
| Protocol | HTTPS |
| Public domain | jellyfin.example.com |
| Path prefix | 留空 |
| Public port | 留空(使用主 HTTPS 入口) |
| Local host | 127.0.0.1 |
| Local port | 8096 |
Agent 使用 host network,因此这里的 127.0.0.1 指 NAS 宿主机。如果目标服务在另一台局域网设备上,也可以填写对应的 LAN 地址,例如 192.168.1.20。
如果前面没配 *.example.com 通配符解析,记得先把 jellyfin.example.com 单独解析到 VPS。
保存后,PortLoom 会分配 VPS 回环端口、建立 SSH 反向转发,并把域名加入内置 Gateway,证书自动申请。
等待状态收敛后访问:
https://jellyfin.example.com如果页面能打开,说明第一条路由已经打通。
怎么看懂三层状态
PortLoom 没有把所有问题压成一个绿色圆点,而是分成三层:
Local
Agent 能否访问 local_host:local_port。
如果 Local down,先在 NAS 上检查:
curl -I http://127.0.0.1:8096如果这里就不通,问题通常在本地服务、监听地址、端口或 NAS 防火墙,与 VPS 和域名无关。
Tunnel
SSH 反向转发是否已经建立。Tunnel down 常见原因包括:
- NAS 无法访问 VPS 的
2222 - SSH 密钥或
known_hosts权限不正确 - VPS 防火墙没有放行
2222 - 远程端口冲突
Public
Agent 是否已经应用最新配置,以及内置 Gateway 是否已经收敛。
Public 显示 published,不代表外部 DNS 和 TLS 一定正确。如果 Local、Tunnel、Public 都正常,但公网仍打不开,就继续检查域名解析、VPS 的 80/443 防火墙和 Server 日志(证书申请也在这里)。
常见故障怎么排
按链路从近到远检查,通常比反复重装更快。
1. 控制台打不开
进入 Server 的项目目录(Compose 模板方式就是你创建的 portloom/ 目录):
docker compose ps -adocker compose logs --tail=100 server sshd确认 DNS 指向 VPS,并检查 80/443 是否放行、是否被其他程序占用。
2. Agent 无法注册
检查:
- 一次性令牌是否已经使用或过期
- NAS 和 VPS 的系统时间是否正确
- NAS 是否能访问管理域名的 HTTPS
- Agent 数据目录是否持久化且可写
注册命令失败后不要删除 Agent 状态目录。修好网络后,先重新执行同一条安装命令,安装器会尝试安全恢复。
3. Gateway 返回 404
一般是 Host 没有匹配到路由。检查公网域名是否填写正确、路由是否启用,以及请求使用的域名是否和 WebUI 完全一致。
4. Gateway 返回 502
这通常表示 VPS 侧没有可用的隧道监听。先看 Tunnel 状态和 Agent 日志,不要先删除路由或数据库。
5. 页面能开,但视频或大文件异常
首页返回 200 只说明基础链路可用。媒体服务还要验证:
- 大文件连续下载
- HTTP Range 请求
- WebSocket(如果应用需要)
- 长连接
- 实际上行带宽
备份和升级
Server 使用 SQLite 保存配置。官方给的备份原则很明确:升级或备份前先停止 server 和 sshd,备份整个项目目录——compose.yml、私密 .env 和完整的 data/,并记录 docker compose images 的输出;不要只复制运行中的 portloom.db 单个文件。
Agent 侧同理,~/.portloom/agent 里的数据目录(含 SSH 私钥、known_hosts 和 agent.json)要一起备份,不要删除后重建。
升级前先看对应版本的 Release Notes。模板固定的是明确的镜像标签,不要在生产环境改成浮动的 latest。日常重启只操作长驻服务(docker compose restart server sshd);配置或镜像变化用 docker compose up -d,一次性的 state-init 不需要单独重启。
官方升级与回滚说明:
几个安全细节
PortLoom 已经把 Server、SSHD 和 Agent 拆开,并对容器做了非 root 运行、只读根文件系统和能力裁剪,但部署者仍需要保护好凭据:
- 不要公开管理员 Token
- 不要把一次性注册命令发给别人
.env、SSH 私钥和备份目录使用严格权限- 不要让 PortLoom 容器访问 Docker Socket
- 管理域名只使用 HTTPS
- 删除路由或重建数据前先备份
- 不要把删除 SQLite、Agent 状态目录或重新注册当成首选排错手段
PortLoom 的管理域名同时承载 Agent API。如果还要叠加来源 IP 限制、VPN 或身份验证网关,必须保证 NAS 仍能访问 Agent 所需的 API;不要直接给整个域名套一层只适合浏览器的登录页,否则心跳和配置同步也会被拦截。
适合哪些场景
PortLoom 适合把这些 Web 服务发布出去:
- Jellyfin、Emby 等媒体服务
- 自建博客和静态站点
- NAS 相册、文件管理页面
- Home Assistant 等家庭服务
- 内网开发环境和测试页面
- v0.4 起还包括数据库、游戏服务器等 TCP/UDP 服务
如果只发布一个临时页面,手写一条 SSH 隧道可能更快。服务多、域名多,或者希望在一个界面里看清本地、隧道和公网状态时,PortLoom 会省下不少重复配置。
项目和文档地址: