docker mihomo 安装问题记录

记录范围

本文记录本机(docker 容器方式)部署 mihomo(clash)代理 + 管理面板过程中遇到的全部问题:现象 → 根因 → 修法 → 客观证据。所有数字都来自本机实测。

环境与形态

项值
部署方式docker compose,跑在 rootless daemon(用户 cat)
目录~/www/mihomo/(compose 与 data/);容器内挂载为 /root/.config/mihomo
镜像metacubex/mihomo:v1.19.24 + 面板 ghcr.io/metacubex/metacubexd:v1.247.0
端口7890 代理;9091 -> 9090 控制 API(避开面板的 9090);9090 -> 80 面板
节点来源proxy-providers 一份订阅(HTTP,interval 1800)
当前实测10MB 样本经代理均速约 574 KB/s;选中节点 新加坡-SG-3-2;容器用量 clash 52MiB / 512MiB、CPU 0.05%

一、问题清单

#现象根因修法
P1走代理下载极慢(nix 拉包慢到不可用)健康检查的测速目标选错 —— 用通用 CDN/测速站时,节点排序完全不可信,自动选择长期停在「对通用站快、对真实下载主机慢」的坏节点上把健康检查 URL 改成真实下载主机(本机场景 = https://cache.nixos.org/nix-cache-info,仅 51 字节)
P2容器跑在 rootful(system)daemon 下,而不是 rootless(cat 用户)NixOS 同时开了两个 daemon;DOCKER_HOST 指向哪个就用哪个一次性搬迁镜像/网络到 rootless(见第三节)。不需要改 .nix
P3并发时代理吞吐上不去compose 里 cpus: 0.5 / mem_limit: 256M —— 半核成为 TLS 解密 + 多并发的天花板,nofile 也偏小cpus 2.0 / mem 512M / nofile 65535 + 健康检查 + 日志轮转
P4换订阅/节点增减就要改配置分组里写死节点名(「死值」)节点只在 provider 定义一次,所有分组一律 use: + filter / exclude-filter 动态匹配
P5调参过程中踩到若干配置项陷阱见第六节逐条见第六节

二、P1:下载慢的真正原因(最有价值的一条)

根因不是带宽、不是配置写错、不是 DNS,而是自动选择长期停在坏节点上:实测代理选中 爱沙尼亚-EE-1 时对 cache.nixos.org 只有 41 KB/s,而同订阅里 澳大利亚-AU-3 有 5183 KB/s —— 相差 126 倍。

为什么排序会选错:通用测速目标会骗人

对 35 个节点逐点实测(每个节点分别下载 cache.nixos.org 与通用测速站 speedtest.tele2.net,原始数据存 clash-sweep-results.json):

节点对 cache.nixos.org(真实目标)对 tele2(通用目标)结论
澳大利亚-AU-35183 KB/s241 KB/s真实最快,通用目标却低估它
荷兰-NL-14243 KB/s4862 KB/s两个口径一致
美国-US-24220 KB/s失败通用目标测不出来
日本-TY-5-HY22057 KB/s390 KB/s被低估
德国-DE-21767 KB/s1724 KB/s一致
英国-UK-1212 KB/s3172 KB/s骗人:差 15 倍
爱沙尼亚-EE-141 KB/s2778 KB/s骗人:差 67 倍 ← 就是当时被选中的那个

另外:35 个节点里有 11 个对 cache.nixos.org 吞吐为 0(完全不通或极慢),包括订阅里的「剩余流量/有效期」这种伪节点行 —— 这些行必须用 exclude-filter 排除,否则会被当成节点参与测速。

修法与效果

把健康检查 URL 从「通用站点」改成真实下载主机上的极小文件:

# 51 字节,几乎零流量 → 所以 interval 可以压到 300 秒,排序跟着实时更新
url: "https://cache.nixos.org/nix-cache-info"
interval: 300
lazy: false          # 启动就测,不要「用到才测」

效果(实测):单线程 0.017 → 0.49 MiB/s(28 倍);8 并发聚合 2.42 MiB/s。

方法论:健康检查的测速目标必须对准你真正要下载的东西。「延迟低」和「下载快」是两件事 —— 有的节点 ping 很低但带宽极差。

三、P2:容器应该跑 rootless,而不是 rootful

现象:以普通用户执行 docker ps 看到空列表,但代理确实在跑。判定证据(两条独立途径都指向 rootful):

# 1) 容器进程的 cgroup
cat /proc/<mihomo pid>/cgroup
  → system.slice/docker-048bc6e0….scope        # rootful 是 system.slice
  → user.slice/user-1000.slice/…/docker-*.scope # rootless 是 user.slice(现在已经是这个)

# 2) 父进程
ps -o ppid= -p <mihomo pid>  → containerd-shim-runc-v2

# 3) DOCKER_HOST 从哪来(/etc/set-environment,由 NixOS 的 setSocketVariable 生成)
if [ -z "$DOCKER_HOST" -a -n "$XDG_RUNTIME_DIR" ]; then
  export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/docker.sock"
fi

迁移步骤(一次性,不联网,避开 Docker Hub 被墙)

# 1) 镜像从 rootful 搬到 rootless
DOCKER_HOST=unix:///var/run/docker.sock docker save \
  metacubex/mihomo:v1.19.24 ghcr.io/metacubex/metacubexd:v1.247.0 | docker load
# 2) rootless 里建同名网络(compose 里是 external: true)
docker network create ghost_net
# 3) 拆掉 rootful 那份
cd ~/www/mihomo && DOCKER_HOST=unix:///var/run/docker.sock docker compose down
# 4) 用 rootless 起(顺带应用新的 compose 资源限制与新 config.yaml)
cd ~/www/mihomo && docker compose up -d

结论:开机自启不需要改 .nix

rootless 侧的声明式条件本来就齐备:systemctl --user is-enabled docker = enabled、Linger=yes、subuid/subgid 已分配、DOCKER_HOST 由 /etc/set-environment 开机自动导出;再加上容器 restart: always,rootless dockerd 启动时会自动恢复容器。本次实测确认:重启后两个容器自动以 rootless 起来(Up (healthy),cgroup 在 user@1000.service/user.slice/docker-*.scope)。

坑:不要用 docker -H unix:///var/run/docker.sock 这种写法 —— 本机实测会卡死;一律用 DOCKER_HOST=… docker … 前置环境变量。迁移的第 3、4 步之间别让两份同时存在(会抢 7890 端口)。

四、P3:compose 资源限制(改前 → 改后)

项改前改后理由
clash cpus0.52.0半核是 TLS 解密 + 多并发的天花板(单节点实测能跑 5MB/s)
clash mem_limit256M512M35 节点订阅 + 规则集偏紧
nofile(默认)65535连接多时文件描述符要够
healthcheck无wget -qO- http://127.0.0.1:9090/version,30s用镜像内自带的 busybox wget(已确认存在)
日志默认(无上限)json-file,max-size 10m × 3避免日志无限增长

现状用量:clash 52MiB / 512MiB、CPU 0.05%;面板 26MiB / 256MiB。

五、P4:配置里不许写死节点名

硬要求:节点只在 proxy-providers 里定义一次,分组一律用 use: + filter / exclude-filter 动态匹配,这样上游订阅节点增减、换订阅都不用改分组:

proxy-providers:
  lite20280602:                       # 订阅只在这里出现一次
    type: http
    url: "<你的订阅链接>"
    interval: 1800

proxy-groups:
  - name: 自动选择
    type: url-test
    use: [lite20280602]               # ← 动态:订阅里的节点自动进组
    exclude-filter: "(?i)v6|剩余|有效期|到期|官网|Expire"   # 排除 v6-only 与到期信息伪节点
    url: "https://cache.nixos.org/nix-cache-info"
    interval: 300
    lazy: false
  - name: 日本
    type: url-test
    use: [lite20280602]
    filter: "(?i)日本|japan|-jp-|-ty|-os"      # 按国家关键词动态圈节点
    exclude-filter: "(?i)v6"

分组清单:节点选择 / 自动选择 / 全部节点 / 下载聚合(load-balance)/ 若干国家组。所有组都指向同一个 provider。

六、P5:调参踩过的具体坑

现象原因改法
某个国家组没有节点、类型被改成 COMPATIBLEfilter 关键词没匹配到任何节点 → 空组用节点名的真实写法做 filter(如 日本|japan|-jp-|-ty|-os),改完重启验证组内节点数
健康检查一直不通过expected-status: 204 与被测站点返回 200 不符要么删掉该字段,要么选返回 204 的探测目标
启动时报废弃项global-client-fingerprint 已废弃删除该行
mihomo 警告测试地址用了 HTTP 明文测试地址改 HTTPS
刚启动那几秒代理不可用、吞吐为 0启动瞬间落到订阅里的第一个节点(可能是坏节点),而健康检查是「懒加载」lazy: false + interval: 300(启动即测、定时重测)
加了一条 nixos.org,DIRECT 规则后 nix 又下不动了本机直连 cache.nixos.org 根本不通(直连超时、0 字节)删掉该规则 —— 这类域名必须走代理

七、交付物与备份

文件说明
~/www/mihomo/data/config-fixed.yaml保守修复版:11 组,纯 filter 动态分组,并行下载规则保留为注释
~/www/mihomo/data/config-githubmix.yaml激进混合版:21 组、7 个 YAML 锚点、应用分流组、广告拦截、并行下载走聚合
~/www/mihomo/data/config.yaml当前生效(内容与 config-fixed.yaml 一致,已核对 md5)
~/www/mihomo/docker-compose.yml资源限制 / 健康检查 / 日志轮转 / 面板后端地址可用 .env 覆盖
~/www/mihomo/backup-20261006-213321/改动前的 config.yaml 与 docker-compose.yml 备份
clash-sweep-results.json35 节点逐点吞吐原始数据(在 Hermes scratch 目录,会被定期清理)

八、验证命令

# 1) 容器跑在哪个 daemon(rootless 才是目标状态)
cat /proc/$(pgrep -x mihomo | head -1)/cgroup      # 应见 user.slice/…/docker-*.scope
DOCKER_HOST=unix:///run/user/1000/docker.sock docker ps      # 应有 clash / clash-ui
DOCKER_HOST=unix:///var/run/docker.sock docker ps            # 应为空

# 2) 当前选中的节点与延迟
curl -s http://127.0.0.1:9091/proxies/自动选择 | python3 -m json.tool | grep -E '"now"|"delay"'

# 3) 真实吞吐(用像样的体积,别用 51 字节的小文件 —— 那只能反映延迟)
curl -x http://127.0.0.1:7890 -o /dev/null --max-time 45 \
  -w "均速=%{speed_download} B/s\n" https://proof.ovh.net/files/10Mb.dat

# 4) 健康检查目标本身是否可达
curl -sS -o /dev/null -w "http=%{http_code} 首字节=%{time_starttransfer}s\n" \
  -x http://127.0.0.1:7890 https://cache.nixos.org/nix-cache-info

九、遗留与注意

    • 实例:同一个荷兰-NL-1,21:20 实测 4243 KB/s,21:55 再测只有 14 KB/s
    • 结论:配置能消除灾难性选错并用并行摊平波动,但保证不了持续高速;要稳定只能换更好的订阅/自建,或始终走「下载聚合」组
    • 它仍写着「当前实际是跑在 rootful 上」和迁移步骤;实际已经跑在 rootless 且重启后自启正常,该注释应更新为「现状:rootless」
    • config-fixed.yaml 里该规则是注释态;要聚合提速就启用「下载聚合」组并把下载类规则指过去
    • 代价:同一域名并发多连接,部分站点可能不友好
    • compose 里用 ${CLASH_API_URL:-http://<宿主局域网IP>:9091},换机器/换网段改 .env 即可,不用改 compose 文件