先釐清 Docker 透明代理的封包路徑
Docker 映像檔下載逾時,常被誤判為 Clash 節點速度不足,但實際上可能在多個不同環節發生。執行 docker pull 時,請求通常不是由正在執行的應用程式容器發出,而是由 Docker daemon 負責連線 Registry。也就是說,容器內設定的 HTTP_PROXY、HTTPS_PROXY,不一定會影響 docker pull;反過來,Docker daemon 已設定代理,也不代表容器內的套件管理器會自動使用相同代理。
一個完整的排查路徑可以拆成四段:Docker CLI 將請求交給 daemon、daemon 解析 Docker Hub 或 GHCR 網域、daemon 連線 Registry 與認證服務,最後下載 manifest、layer 與簽章資料。Docker Hub 可能涉及 registry-1.docker.io、auth.docker.io 以及 CDN 網域;GHCR 則通常會使用 ghcr.io、GitHub API 或物件儲存 CDN。因此,只允許其中一個網域通過規則,仍可能在取得 Token 或下載 layer 時逾時。
| 流量來源 | 常見目的 | 主要代理設定位置 |
|---|---|---|
| Docker daemon | docker pull、映像檔推送與 Registry 認證 |
systemd drop-in、Docker Desktop 設定或 daemon.json |
| 建置容器 | apt、npm、pip、go mod |
BuildKit 參數、Dockerfile ARG 或建置環境 |
| 執行中的容器 | 應用程式 API、外部套件與更新服務 | 容器環境變數、網路模式與應用程式設定 |
| 宿主機 mihomo | 接收 HTTP、SOCKS 或 TUN 轉送流量 | mixed-port、TUN、路由與規則設定 |
建立宿主機 mihomo 透明代理基礎
在 Linux 宿主機上,較容易維護的架構是由 mihomo 負責代理出站流量,Docker daemon 透過宿主機可達的 HTTP 或 SOCKS 連接埠使用代理。若需要接管不支援代理環境變數的程式,再額外啟用 TUN。不要一開始同時啟用多個透明代理工具、iptables 轉送規則與 TUN,否則很難判斷封包究竟在哪一層被重導。
以下是一組適合單機測試的核心設定片段。mixed-port 提供 HTTP 與 SOCKS 混合代理,allow-lan 讓 Docker bridge 網段可以連到宿主機;若只把連接埠繫結在 127.0.0.1,容器通常無法直接使用它。實際部署時應使用防火牆限制來源,不要無限制暴露代理連接埠。
mixed-port: 7890
allow-lan: true
bind-address: "*"
mode: rule
log-level: info
external-controller: 127.0.0.1:9090
secret: replace-with-a-long-secret
dns:
enable: true
listen: 0.0.0.0:1053
ipv6: false
enhanced-mode: fake-ip
nameserver:
- https://1.1.1.1/dns-query
- https://dns.google/dns-query
若採用 TUN,請確認目前的 mihomo 版本與作業系統核心都支援所需功能,再逐項開啟自動路由、嚴格路由與 DNS 劫持。TUN 不是單純的「代理開關」,它會改變系統路由與 DNS 流程。設定錯誤時,可能造成 mihomo 自己的控制連線被再次導回 TUN,形成迴圈。
tun:
enable: true
stack: system
auto-route: true
auto-detect-interface: true
strict-route: true
dns-hijack:
- any:53
- tcp://any:53
Docker bridge 與宿主機位址
Linux Docker 容器通常位於 172.17.0.0/16 或自訂 bridge 網段。容器中的 127.0.0.1 指向容器自身,不是宿主機,因此不能把代理寫成 http://127.0.0.1:7890。可以使用 Docker bridge 的閘道位址,例如透過 ip route 查看:
ip route
docker network inspect bridge
ss -lntp | grep 7890
在許多 Linux 預設環境中,容器可用宿主機 bridge 位址 172.17.0.1 連線,但這不是所有自訂網路都固定相同。使用 Docker Compose 時,也可以加入宿主機映射,然後以固定名稱存取;不過 host-gateway 主要解決名稱解析,仍要確認宿主機代理服務確實監聽在可達的介面上。
services:
worker:
image: example/worker:latest
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
HTTP_PROXY: http://host.docker.internal:7890
HTTPS_PROXY: http://host.docker.internal:7890
NO_PROXY: localhost,127.0.0.1,.local
設定 Docker daemon 的出站代理
處理 docker pull 最重要的是設定 daemon,而不是只在目前終端機執行 export HTTPS_PROXY=...。在使用 systemd 的 Linux 主機上,可以建立 Docker 服務的 drop-in 檔案。以下範例假設 mihomo 的 HTTP 代理位於宿主機 127.0.0.1:7890;如果 Docker daemon 在另一台機器或獨立虛擬機中執行,則必須改成該 daemon 能連線的宿主機位址。
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf >/dev/null <<'EOF'
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:7890"
Environment="HTTPS_PROXY=http://127.0.0.1:7890"
Environment="NO_PROXY=localhost,127.0.0.1,::1,.local"
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl show --property=Environment docker
修改後先用一個小型公開映像測試,不要直接以大型映像作為唯一判斷依據。若 daemon 沒有讀到環境變數,systemctl show 通常能立即揭露問題。若使用 Docker Desktop,代理設定通常由 Desktop 的設定介面管理,直接修改 Linux 主機上的 systemd 檔案未必會影響 Desktop 內部的 daemon。
代理服務使用 HTTPS 連接埠時,URL 方案必須與實際服務一致。mihomo 的 mixed-port 多數情況是以 HTTP 代理協定接收 CONNECT 請求,即使目標網址是 HTTPS,Docker daemon 的代理 URL 仍可寫成 http://宿主機:7890。不要因為目標是 HTTPS,就擅自將代理 URL 改成 https://;這會讓 daemon 嘗試以 TLS 連線到一個其實只提供純 HTTP 代理的連接埠。
Docker daemon 與容器代理不要混淆
Docker daemon 的設定只影響映像檔拉取、推送與部分 Registry 操作,不會自動注入每一個容器。容器需要代理時,應在 Compose、執行命令或建置流程中明確傳入。反過來,容器中的環境變數也不會修正 daemon 的 Registry 連線。
docker run --rm \
-e HTTP_PROXY=http://host.docker.internal:7890 \
-e HTTPS_PROXY=http://host.docker.internal:7890 \
-e NO_PROXY=localhost,127.0.0.1,host.docker.internal \
curlimages/curl:latest \
-I https://registry-1.docker.io/v2/
回應 401 Unauthorized 不一定是故障。Docker Registry 常用未認證的 /v2/ 回應告知客戶端前往 Token 服務,這反而證明 DNS、TCP、TLS 與基本 HTTP 路徑已經走通。真正需要注意的是連線逾時、TLS handshake timeout、connection reset 或無法解析主機名稱。
DNS 與分流規則的關鍵排查
透明代理環境中,DNS 解析與實際出站路徑必須保持一致。若宿主機使用 mihomo 的 fake-ip,但 Docker 容器仍直接詢問公司 DNS 或路由器 DNS,可能出現解析結果不同、內部網域無法存取,或 Registry 網域解析到在目前網路不可達的位址。若只想先排查 Docker Hub,建議暫時使用簡單的 redir-host 或明確 DNS 設定,確認基本連線後再啟用較複雜的 fake-ip 規則。
先從宿主機與容器分別測試 DNS,不要只在瀏覽器中確認網頁能否開啟:
getent hosts registry-1.docker.io
getent hosts auth.docker.io
getent hosts ghcr.io
docker run --rm busybox nslookup registry-1.docker.io
docker run --rm busybox nslookup ghcr.io
規則模式下,Docker Hub、GHCR、GitHub API 及其 CDN 不應被錯誤送往直連。可以先建立明確的網域規則,再依日誌補充實際出現的 CDN 網域。規則順序由上而下比對,因此自訂的 Registry 規則要放在寬泛的 GEOIP、MATCH 或其他兜底規則之前。
rules:
- DOMAIN-SUFFIX,docker.io,PROXY
- DOMAIN-SUFFIX,docker.com,PROXY
- DOMAIN-SUFFIX,dockerusercontent.com,PROXY
- DOMAIN-SUFFIX,ghcr.io,PROXY
- DOMAIN-SUFFIX,github.com,PROXY
- DOMAIN-SUFFIX,githubusercontent.com,PROXY
- DOMAIN-SUFFIX,githubassets.com,PROXY
- MATCH,DIRECT
上述規則名稱中的 PROXY 必須與實際存在的策略組名稱完全一致。若你的策略組叫做「節點選擇」或 Proxy,就必須替換成對應名稱。不要只把所有網域送往代理而忽略內部 Registry;企業環境中的 registry.internal.example、服務發現名稱與私有 DNS,通常應使用 DIRECT 或內部專用策略。
用日誌與分段測試定位逾時
排查時不要一次修改代理、DNS、TUN 和規則。建議先關閉不必要的 TUN,只讓 Docker daemon 使用 mihomo 的 HTTP 代理;確認可以拉取小型映像後,再決定是否需要透明接管。這樣可以把問題縮小到「daemon 代理設定」或「TUN 路由」其中一層。
- 在宿主機確認 mihomo 正在監聽
7890,並查看控制介面與核心日誌沒有持續重啟。 - 用
curl經由同一個代理請求 Registry,區分 DNS、TLS 與 HTTP 狀態碼問題。 - 重新啟動 Docker daemon,確認 systemd 環境變數已載入。
- 先拉取小型測試映像,再測試包含多個 layer 的實際映像。
- 若主體映像成功但建置失敗,改查 BuildKit、Dockerfile 與套件管理器的代理設定。
curl -v \
-x http://127.0.0.1:7890 \
--connect-timeout 10 \
--max-time 30 \
https://registry-1.docker.io/v2/
docker --debug pull hello-world:latest
journalctl -u docker --since "10 minutes ago" --no-pager
mihomo 日誌中的 dial tcp 通常代表建立 TCP 連線階段遇到問題;context deadline exceeded 表示在期限內沒有完成操作,但仍需配合目標網域判讀;tls: handshake timeout 常見於 TLS 握手被阻擋、代理鏈品質不穩或 MTU 不合;no such host 則優先檢查 DNS。若日誌顯示請求走了 DIRECT,而你的網路需要代理,規則比對就是第一個修正方向。
Docker layer 下載時出現逾時,也可能是 MTU 或連線重用問題,尤其是在 TUN、WireGuard、PPPoE 或多層虛擬網路並存的主機上。不要先大幅調整 MTU;應先比較一般 HTTPS、小型映像與大型 layer 的結果,再查看核心網路介面與路由。若只有大檔案在傳輸中途重設,才有理由進一步測試較低的介面 MTU、停用某些鏈路卸載功能,並確認代理節點對長連線的支援。
正式環境的安全與維護方式
測試成功後,應把設定拆成可追蹤、可回復的檔案,不要只依賴手動執行的 export 或臨時 iptables 指令。Docker daemon 的代理設定、mihomo 的核心設定、規則集與防火牆政策應分開備份,並記錄變更時間、版本與回復方式。升級 mihomo 或 Docker 前,先保留目前能正常工作的設定與日誌。
- 限制監聽範圍:若只服務本機 Docker,優先使用本機位址;若 Docker 位於 bridge 或獨立主機,僅放行必要的 bridge 網段與管理網段。
- 保護控制介面:
external-controller不應直接暴露到公網,並且必須設定足夠長的secret。 - 避免代理環迴:將 mihomo 本身、DNS、控制介面、內部 Registry 與必要的管理端點加入
no_proxy或直連規則。 - 固定更新節奏:不要在正式環境直接追蹤未測試的核心或規則集變更;先於測試主機驗證,再逐步推出。
- 記錄失敗指標:保留 Docker daemon 日誌、mihomo 連線日誌、DNS 失敗次數與 layer 下載逾時時間,方便比較節點或網路變更前後的差異。
若環境中有私有 Registry,建議明確區分公開與內部網域。公開 Docker Hub、GHCR 可依需求走代理,內部 Registry 則通常應走內網,並在 Docker daemon 的 NO_PROXY 中加入主機名稱、網域後綴與必要的 IP 範圍。NO_PROXY 的格式在不同程式中略有差異,修改後應以實際 daemon 日誌與測試拉取結果確認,不要假設所有元件都完全支援 CIDR。
當 Docker Hub、GHCR 和套件下載都需要穩定通過時,最重要的不是堆疊更多規則,而是確認每個流量來源都有清楚的出口。Docker daemon、BuildKit、執行容器與宿主機 TUN 各自有不同的設定邊界;先沿著封包路徑定位,再用日誌驗證實際分流,才能避免把 DNS 問題誤當成節點問題,也能讓正式環境的透明代理更容易維護。