TL;DR
如果你只想先把 MeshMonitor 在飞牛 NAS 上跑起来,可以先记住下面几件事:
- Compose 写了
env_file: .env,项目目录里就必须真的存在.env;不想单独维护文件,就删掉env_file,直接使用environment。 - 官方镜像是
ghcr.io/yeraze/meshmonitor:latest。国内网络拉取很慢时,可以临时使用可信的 GHCR 镜像代理,但要接受同步延迟和第三方供应链风险。 - 正常情况下应该优先使用 Docker Bridge,并映射
8080:3001。这也是 MeshMonitor 官方文档推荐的方式。 - 如果容器内访问
3001正常、NAS 本机访问映射端口也正常,但同一局域网里的电脑和手机都超时,问题很可能出在 NAS 的 Docker Bridge 入站转发路径,而不是 MeshMonitor 本身。 - 本文测试的飞牛 fnOS 环境最终通过
network_mode: host绕过了 Bridge 转发问题。 - Host 模式下
ports映射无效,浏览器要访问应用实际监听的端口。本次镜像实际监听3001,所以最终地址是http://NAS-IP:3001。 ALLOWED_ORIGINS必须与浏览器地址完全一致,包括协议、IP 和端口。- 首次登录账号通常是
admin,密码是changeme,登录后务必立即修改。
本文最终可用的配置如下。请把示例中的两个 IP 换成自己的地址:
services:
meshmonitor:
image: ghcr.nju.edu.cn/yeraze/meshmonitor:latest
container_name: meshmonitor
network_mode: host
restart: unless-stopped
volumes:
- meshmonitor-data:/data
environment:
- TZ=Asia/Shanghai
- MESHTASTIC_NODE_IP=192.168.0.246
- ALLOWED_ORIGINS=http://192.168.0.200:3001,http://localhost:3001
volumes:
meshmonitor-data:
driver: local
部署完成后访问:
http://192.168.0.200:3001
上面的南京大学镜像地址是第三方代理示例,并非 MeshMonitor 官方镜像仓库。如果你的网络可以正常访问 GHCR,优先换回
ghcr.io/yeraze/meshmonitor:latest。
这次部署的环境
这次排障使用的环境如下:
- NAS:飞牛 fnOS,排障时版本为
1.2.0203 - NAS 局域网 IP:
192.168.0.200 - Meshtastic 节点 IP:
192.168.0.246 - Meshtastic TCP 端口:
4403 - MeshMonitor:Docker Compose 部署
- 目标:让局域网中的电脑和手机都能打开 MeshMonitor
IP 地址只是示例。你的 NAS 可能是 192.168.1.10,节点可能是 192.168.1.20,替换时不要照抄。
第一坑:env file .../.env not found
最开始遇到的报错是:
env file /vol1/1000/Docker/meshmonitor/.env not found:
stat /vol1/1000/Docker/meshmonitor/.env: no such file or directory
原因很直接:Compose 中写了下面这一行:
env_file: .env
这等于告诉 Docker Compose:“请到 Compose 文件所在目录读取 .env。”但对应目录里并没有这个文件,Compose 在创建容器前就会停止。
有两种正确处理方式。
方案 A:不用单独的 .env
删除:
env_file: .env
把配置直接写在:
environment:
- TZ=Asia/Shanghai
- MESHTASTIC_NODE_IP=192.168.0.246
这种方式对刚接触飞牛 Docker 的用户更直观,所有配置都在同一个页面里。
方案 B:真的创建 .env
如果你使用 MeshMonitor 官方的 Compose 配置生成器,或者希望把变量单独存放,就在报错指向的项目目录中创建 .env:
TZ=Asia/Shanghai
MESHTASTIC_NODE_IP=192.168.0.246
ALLOWED_ORIGINS=http://192.168.0.200:8080
然后在 Compose 中保留:
env_file: .env
不要只在 Compose 编辑器里写 env_file: .env,却忘了在文件管理器中创建文件。
第二坑:YAML 缩进看起来简单,实际上很严格
Compose 是 YAML。ports、volumes 和 environment 下面的列表必须继续缩进:
services:
meshmonitor:
volumes:
- meshmonitor-data:/data
environment:
- TZ=Asia/Shanghai
下面这种写法是错误的:
services:
meshmonitor:
volumes:
- meshmonitor-data:/data
飞牛的编辑器有时会让缩进不够显眼。遇到“mapping”“sequence”或“解析 YAML”一类报错时,先检查空格,不要急着怀疑镜像。
第三坑:GHCR 在国内拉取很慢
MeshMonitor 官方镜像地址是:
image: ghcr.io/yeraze/meshmonitor:latest
国内网络访问 GitHub Container Registry 时,可能出现速度很慢、长时间卡住或超时。本文测试时改用南京大学的 GHCR 代理后,镜像很快就拉取完成:
image: ghcr.nju.edu.cn/yeraze/meshmonitor:latest
这里要耐心区分“镜像代理”和“镜像本身”:
yeraze/meshmonitor仍然是原项目路径;- 前面的 Registry 域名变成了第三方代理;
- 代理可能比官方仓库晚同步;
- 第三方代理的可用性和可信度需要自行评估;
- 遇到版本异常时,应该切回官方 GHCR 对照验证。
如果准备长期运行,还可以把 latest 换成明确版本号,避免一次更新同时引入应用变化和部署变化。
先从官方推荐的 Bridge 配置开始
不是每台飞牛 NAS 都会遇到本文后面的网络问题。正常情况下,应该先使用 MeshMonitor 官方推荐的 Bridge 方案:
services:
meshmonitor:
image: ghcr.io/yeraze/meshmonitor:latest
container_name: meshmonitor
ports:
- "8080:3001"
restart: unless-stopped
volumes:
- meshmonitor-data:/data
environment:
- TZ=Asia/Shanghai
- MESHTASTIC_NODE_IP=192.168.0.246
- ALLOWED_ORIGINS=http://192.168.0.200:8080,http://localhost:8080
volumes:
meshmonitor-data:
driver: local
这里的含义是:
- MeshMonitor 在容器内部监听
3001; - Docker 将 NAS 的
8080转发到容器的3001; - 浏览器访问
http://192.168.0.200:8080; - 数据保存在 Docker 卷
meshmonitor-data中,重建容器不会自动清空数据。
Docker 官方文档说明,Bridge 容器的端口默认不能直接从宿主机外访问;配置 8080:3001 后,Docker 会创建对应的端口发布和防火墙规则。
如果这一配置能直接打开页面,就不需要改成 Host 模式。Bridge 的网络隔离更清晰,也更容易只暴露需要的端口。
第四坑:容器显示“正在运行”,不等于局域网一定能打开
这次最费时间的地方,是飞牛界面显示容器正在运行,但浏览器访问:
http://192.168.0.200:8080
一直超时。
这时候不要反复删除、重装容器。应该把问题拆成四层:
- 应用进程有没有启动;
- 容器内部有没有监听端口;
- Docker 有没有建立端口映射;
- 局域网流量能不能穿过 NAS 防火墙和 Docker 转发链。
检查 Docker 端口映射
sudo docker inspect meshmonitor --format '{{json .NetworkSettings.Ports}}'
正常的 Bridge 输出类似:
{
"3001/tcp": [
{"HostIp":"0.0.0.0","HostPort":"8080"},
{"HostIp":"::","HostPort":"8080"}
]
}
这只能证明 Docker 记录了映射,不能单独证明远端访问已经成功。
检查容器内部监听端口
MeshMonitor 镜像中可能没有 ss,可以用 netstat:
sudo docker exec meshmonitor sh -lc 'ss -lntp || netstat -lntp'
本次看到的关键输出是:
tcp 0 0 :::3001 :::* LISTEN
这说明 MeshMonitor Web 服务已经启动,并监听所有地址的 3001。
从容器内部请求应用
sudo docker exec meshmonitor sh -lc \
'wget -S -O /dev/null http://127.0.0.1:3001 2>&1'
如果返回 HTTP/1.1 200 OK,应用本身是健康的。
从 NAS 本机请求发布端口
curl -I --max-time 5 http://127.0.0.1:8080
curl -I --max-time 5 http://192.168.0.200:8080
本次两条命令都返回了 200 OK,说明:
- MeshMonitor 正常;
- 容器内部端口正常;
- Docker 的本机端口发布正常;
- NAS 自己通过局域网 IP 访问也正常。
但同一局域网中的 Mac 和手机仍然超时。这一步非常关键,因为它排除了浏览器缓存、Mac 代理和单一客户端故障。
检查 Docker NAT 和转发规则
sudo iptables -t nat -S DOCKER | grep -E '8080|4404'
sudo iptables -nvL FORWARD --line-numbers
sudo iptables -nvL DOCKER-USER --line-numbers
sudo iptables -nvL DOCKER --line-numbers
本次能够看到 8080 被 DNAT 到 172.18.0.2:3001,DOCKER 链也确实有数据包命中 3001 的 ACCEPT 规则。
换句话说,请求已经到达 Docker 转发规则,但局域网客户端没有完成连接。即使继续在飞牛防火墙中放行 8080、3001 和 4404,问题仍然存在。
这里需要谨慎描述:我们确认的是这台设备、这个 fnOS 版本上的实际现象,并没有据此断言所有飞牛 NAS 都存在同一个缺陷。由于系统没有预装 tcpdump,本次没有继续追到最终丢包位置。
第五坑:误以为是 Mac、Clash 或 Tailscale
排障过程中,Mac 同时运行过 Clash Verge 和 Tailscale,因此很容易先怀疑代理或路由冲突。
我们依次做了以下测试:
- 关闭 Mac 上的 Clash Verge;
- 关闭 Mac 上的 Tailscale;
- 关闭 NAS 上的 Tailscale;
- 强制从 Mac 的局域网地址访问;
- 换同一 Wi-Fi 下的手机访问。
结果电脑和手机都无法访问,而 NAS 本机可以返回 200。因此,这次问题不在某一台客户端,也不应该继续围绕浏览器插件或系统代理打转。
排障时最好遵循这个顺序:先换设备,再关代理,最后才修改 Docker 网络。这样可以避免因为一个偶然现象,把 Compose 改得越来越复杂。
最终方案:使用 Host 网络绕过 Bridge 转发
最终使用:
network_mode: host
Host 模式下,容器共享 NAS 的网络命名空间,不再拥有独立的 Bridge IP,也不再经过 8080:3001 这种 NAT 端口映射。
Docker 官方文档明确说明:Host 模式下 ports、-p 和 --publish 都会被忽略。因此 Compose 中必须删除整个 ports 部分。
最终配置为:
services:
meshmonitor:
image: ghcr.nju.edu.cn/yeraze/meshmonitor:latest
container_name: meshmonitor
network_mode: host
restart: unless-stopped
volumes:
- meshmonitor-data:/data
environment:
- TZ=Asia/Shanghai
- MESHTASTIC_NODE_IP=192.168.0.246
- ALLOWED_ORIGINS=http://192.168.0.200:3001,http://localhost:3001
volumes:
meshmonitor-data:
driver: local
飞牛防火墙至少需要允许可信局域网访问 TCP 3001。保存配置、重新创建容器后,可以检查:
sudo docker exec meshmonitor sh -lc 'netstat -lntp'
本次部署实际看到:
tcp 0 0 :::3001 :::* LISTEN
于是正确访问地址是:
http://192.168.0.200:3001
电脑和手机此时都能正常打开页面。
第六坑:设置了 PORT=8080,实际仍监听 3001
MeshMonitor 配置文档把 PORT 列为可选环境变量,默认值是 3001。但在这次使用的镜像和部署环境中,即使写了:
- PORT=8080
netstat 仍显示服务监听 3001,访问 NAS 的 8080 会立即得到“连接被拒绝”。
这里不要凭配置猜端口,要以容器中的实际监听结果为准:
sudo docker exec meshmonitor sh -lc 'netstat -lntp'
为了减少歧义,最终配置删除了无效的 PORT=8080,直接使用应用实际监听的 3001。
如果你一定要保留 http://NAS-IP:8080,可以在飞牛中配置反向代理:
192.168.0.200:8080 → 127.0.0.1:3001
此时还要把 http://192.168.0.200:8080 加入 ALLOWED_ORIGINS。如果只是家庭局域网使用,直接访问 3001 更简单。
第七坑:ALLOWED_ORIGINS 不是随便填一个地址
MeshMonitor 会检查浏览器请求来源。下面三个来源彼此不同:
http://localhost:3001
http://192.168.0.200:3001
http://192.168.0.200:8080
协议、主机名或端口只要有一个不同,就属于不同 Origin。
局域网通过 NAS IP 和 3001 访问时,应写:
- ALLOWED_ORIGINS=http://192.168.0.200:3001,http://localhost:3001
不要为了省事长期使用:
- ALLOWED_ORIGINS=*
通配符适合短时间定位 CORS 问题,不适合长期部署。
如果页面能打开,但登录、接口请求或 WebSocket 异常,也要重新检查这里,而不是只检查端口。
检查 MeshMonitor 能否连接 Meshtastic 节点
Web 页面能打开,和 MeshMonitor 能否连接节点,是两个独立问题。
首先在 NAS 上检查节点的 TCP 端口:
nc -vz 192.168.0.246 4403
再从容器网络中检查:
sudo docker exec meshmonitor node -e \
'const n=require("net");const s=n.connect(4403,"192.168.0.246",()=>{console.log("4403 connected");s.end()});s.on("error",console.error)'
看到:
4403 connected
只能证明 TCP 端口可达。如果日志中仍反复出现 connected、disconnected 或 Not connected to Meshtastic node,继续检查:
- 节点 Wi-Fi 或以太网是否稳定;
- 节点的 TCP 服务是否正常;
- 是否有手机、电脑上的其他客户端同时连接并争用设备;
- MeshMonitor 后台
Dashboard → Sources中保存的 IP 和端口是否正确。
MeshMonitor 4.0 之后,MESHTASTIC_NODE_IP 和 MESHTASTIC_TCP_PORT 主要用于第一次启动时创建初始 Source。数据卷中已经有配置后,再修改环境变量不一定会覆盖现有 Source,应该进入 Dashboard → Sources 修改。
查看实时日志可以使用:
sudo docker logs -f meshmonitor
关于 4404:不要和节点的 4403 混淆
Meshtastic 节点通常在 TCP 4403 等待 MeshMonitor 连接。数据方向是:
MeshMonitor → Meshtastic 节点 IP:4403
4404 则通常与 MeshMonitor 的 Virtual Node 功能有关。启用后,手机端可以把 MeshMonitor 当成一个 TCP 节点连接。没有启用 Virtual Node 时,不必因为官方示例或旧配置中出现过 4404,就认定它一定应该处于监听状态。
Host 模式的代价和安全注意事项
Host 模式解决了这次飞牛 Bridge 转发异常,但它不是没有代价。
1. 网络隔离更弱
容器直接共享 NAS 网络。容器里所有监听 0.0.0.0 或 :: 的服务,都可能直接出现在 NAS 的端口上。
本次使用 netstat 时,除了 MeshMonitor 的 3001,还观察到内部 Python/Apprise 服务监听 8000。请根据实际功能检查端口,不要只关注 3001:
sudo docker exec meshmonitor sh -lc 'netstat -lntp'
防火墙来源 IP 最好限制为可信局域网,而不是无条件允许所有来源。
2. 端口冲突会直接导致启动失败
如果 NAS 上已经有其他服务占用 3001 或 8000,Host 模式下 MeshMonitor 无法再次绑定同一端口。部署前可以检查:
sudo netstat -lntp | grep -E ':3001|:8000'
3. 不要直接暴露到互联网
家庭局域网部署可以使用 HTTP,但不要直接在路由器上把 3001 映射到公网。需要公网访问时,优先使用 VPN,或者按照 MeshMonitor 官方生产部署说明配置 HTTPS 反向代理、强随机 SESSION_SECRET、TRUST_PROXY=true、COOKIE_SECURE=true,并把 ALLOWED_ORIGINS 精确设置为 HTTPS 域名。
4. 立即修改默认密码
首次启动通常会创建:
用户名:admin
密码:changeme
页面打开后第一件事不是研究地图,而是修改管理员密码。
数据备份与更新
本文配置使用命名卷:
volumes:
- meshmonitor-data:/data
这意味着重建容器时数据仍保留,但删除 Docker 卷会丢失数据库。更新前可以先检查卷并备份数据。
常规更新命令是:
docker compose pull
docker compose up -d
如果使用国内镜像代理,更新后发现版本没有变化,可能是代理尚未同步。此时切回官方 GHCR 拉取,或者使用明确版本号核对。
更新完成后建议重新检查:
sudo docker logs --tail 100 meshmonitor
sudo docker exec meshmonitor sh -lc 'netstat -lntp'
一份可以照着走的排障清单
以后再遇到“容器在运行,但页面打不开”,可以按下面顺序检查:
- Compose 是否因为
.env不存在或 YAML 缩进错误而根本没有创建容器; docker ps中容器是否持续运行,而不是反复重启;docker logs --tail 200 meshmonitor是否有启动错误;- 容器内是否真的监听
3001; - 容器内请求
127.0.0.1:3001是否返回 200; - Bridge 模式下,
docker inspect是否存在8080:3001映射; - NAS 本机访问
127.0.0.1:8080是否成功; - NAS 本机访问
NAS-IP:8080是否成功; - 同一局域网中的另一台电脑和手机是否都失败;
- 飞牛防火墙是否放行了实际访问端口;
- Bridge 转发仍异常时,再考虑 Host 模式;
- Host 模式下删除
ports,以netstat看到的实际端口为准; - 把实际浏览器地址完整写入
ALLOWED_ORIGINS; - 页面打开后,再单独排查 Meshtastic 节点的
4403连接。
这套顺序的重点是:每一步只验证一层。不要在还没确认应用是否监听时就反复修改防火墙,也不要因为浏览器打不开,就立即断定镜像启动失败。
结语
这次折腾最容易误导人的地方,是每一层看起来都“差一点就正常”:镜像终于下载好了,容器显示正在运行,应用返回 200,Docker 的 DNAT 规则存在,防火墙规则也生效,但局域网设备就是打不开。
真正有用的不是记住某一条神奇命令,而是把路径拆开:
浏览器 → NAS 防火墙 → Docker 端口发布 → 容器监听 → MeshMonitor → Meshtastic 节点:4403
本文环境最终通过 Host 网络绕开了 Bridge 转发问题,并以应用实际监听的 3001 对外提供服务。其他飞牛用户如果 Bridge 配置可以正常工作,继续使用官方推荐方案即可;只有在证据表明“容器和 NAS 本机都正常,但所有局域网客户端都失败”时,才值得切换 Host 模式。
相关资料:
本文作者: Hays Chan | 陈希
本文链接: https://meshcn.net/meshmonitor-fnos-docker-troubleshooting/