為什麼「Clash 已開」卻換不到 Docker 順利拉映像?
桌面版 Clash 多半透過系統代理或 TUN/虛擬網卡把應用程式流量導去訂閱節點;瀏覽器、部分 GUI 工具會自動跟隨。相對地,docker 指令只是把需求交給本機的 Docker 引擎(dockerd),真正對外連線到 registry 的往往是引擎行程,而不是你當前終端機裡的 shell。若引擎沒有設定代理,或代理位址在引擎視角下無法連線,就會出現「同一台機器上,網頁能開、docker pull 卻一直重試」的現象。
另一個常見誤會是把 export HTTP_PROXY=... 寫在 ~/.bashrc 就以為萬無一失。對使用者層級的 curl、wget、git 通常有效,但作為系統服務的 dockerd 未必繼承你互動式 shell 的環境變數;在 Linux 上往往需要 systemd 的覆寫或 Docker 官方文件建議的方式,才能把代理穩定餵給引擎。若你也在 WSL2 裡操作,請先釐清「指令究竟跑在 Windows、WSL 還是遠端 Linux」,再對照本站WSL2 與 Windows Clash 代理把宿主 IP 與埠畫清楚,否則會把兩種場景的錯誤混在一起。
最後,TLS 握手失敗不一定是「節點爛」,也可能是公司中間人、防毒 HTTPS 掃描、或系統時間偏差;但跨境網路下,更常見的是連線路徑沒有走你預期的出口。接下來先把「誰要設代理」拆開,再談位址要填哪裡。
先畫三條線:引擎、建置(BuildKit)與執行中容器
實務上建議把需求拆成三類,分別檢查代理是否生效。第一類是映像拉取與 registry API:例如 docker pull、docker manifest,主要由 Docker 引擎對外連線。第二類是映像建置:啟用 BuildKit 時,docker build 或 docker buildx build 的部份步驟會在獨立環境中執行 Dockerfile 指令,RUN apt-get、RUN curl 是否走代理,取決於 build 階段有沒有帶入 HTTP_PROXY 或 build-arg。
第三類是執行中容器:docker run 或 Docker Compose 啟動的服務,只有在容器內行程主動支援代理環境變數時才會生效;與「拉映像」是兩回事。舉例來說,資料庫容器未必讀 HTTPS_PROXY,但你在容器內執行的 CLI 工具可能會讀。若把三類混成一鍋,很容易「pull 修好了、build 仍失敗」或反過來。
與純 Linux 無圖形介面只跑 Clash 伺服器的情境相比,桌面使用者通常還多了一層 GUI 與系統代理切換;若你需要在伺服器上以 systemd 常駐 mihomo,可併讀Linux 無圖形介面部署 Clash Meta,把「誰監聽哪個埠、誰負責開機啟動」先釐清,再回到本文的 Docker 代理細節。
宿主機 Clash 要填哪個位址?127.0.0.1 與 host.docker.internal
Clash 常見會開混合埠(HTTP 與 SOCKS 並存)或分開監聽。對 Docker 來說,重點是:從發起連線的那個網路命名空間看去,代理位址必須是可路由的。在宿主終端機裡,127.0.0.1:7890 通常能指到 Clash;但在bridge 網路的容器內,127.0.0.1 往往指向容器自己,並非宿主機。
在 Docker Desktop(macOS/Windows)上,常可使用 host.docker.internal 作為通往宿主的名稱;在 Linux 原生 Docker 則依版本與設定而定,實務上更穩的是使用宿主在 docker0 或區網上的 IP(例如 172.17.0.1 或你的區網閘道),並確認 Clash 有允許來自區網/容器網段的連線,必要時檢查防火牆入站規則。若你只在本機測試,亦可參考本站Windows 區網分享 Clash裡「允許區網」與埠號對齊的觀念,把「誰能連進混合埠」想清楚。
將代理字串寫進環境變數時,請同時準備 http_proxy/https_proxy 與大寫版本,因為不同工具慣例不一;若 Clash 提供 SOCKS,亦可使用 ALL_PROXY=socks5://...,但須確認拉映像/建置鏈路中的工具是否支援。
讓 Docker 引擎本體走代理:daemon 設定與 Linux systemd
若你希望在主機上全面讓引擎拉映像都走代理,應優先查閱所使用 Docker 版本官方文件中的「daemon proxy」做法。常見路徑包含:在 Docker 設定目錄提供 config.json(daemon 層級,而非使用者 ~/.docker/config.json 混淆)、或以 systemd 的 Environment= 覆寫 docker.service。重點是變數要落在dockerd 行程上,而不是只有你輸入指令的那個 shell。
在編輯 systemd 覆寫檔時,建議使用 systemctl edit docker 產生 drop-in,避免直接改發行版套件檔案,升級時被覆蓋。修改後記得 daemon-reload 並重啟 Docker。若代理需要認證或公司內部 CA,還要同步處理憑證信任與 NO_PROXY,否則會看到間歇性 TLS 錯誤。
下列片段僅示意結構,實際鍵名與路徑請以官方文件為準:
# Example drop-in for systemd — verify paths for your distro
# /etc/systemd/system/docker.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://172.17.0.1:7890"
Environment="HTTPS_PROXY=http://172.17.0.1:7890"
Environment="NO_PROXY=localhost,127.0.0.1,::1"
~/.docker/config.json:客戶端 proxies 與行為差異
使用者目錄下的 config.json 可設定 proxies,影響客戶端行為的部份路徑(例如部分 CLI 操作),但不能取代引擎層級對 docker pull 的完整語意;不同版本與後端(moby/buildx)細節會演進,實務上仍要以「拉映像是否仍失敗」為準做驗證。建議把 config.json 視為輔助,與 daemon/Compose 設定互相對照,而不是只改這一個檔案就結案。
若團隊使用私有 registry,請在 NO_PROXY 或 Docker 的不走代理清單中排除內網網段,避免「內部倉庫反而被送去海外節點」的意外。對應的規則設計若牽涉到 Clash 的 DOMAIN/IP 規則,亦可回頭複習YAML 規則分流教學,把「哪些網域一定要直連」寫清楚。
Docker Compose:build 與 services 環境變數怎麼拆?
在 Compose 檔中,建置階段與執行階段要分開設定。建置時若 Dockerfile 內有 RUN curl、RUN npm install,需要透過 build.args 把 HTTP_PROXY 傳入,或在 Dockerfile 以 ARG 接收後設為 ENV。執行中的服務若要存取外網 API,則在 services.<name>.environment 或 env_file 設定;兩者不會自動互相繼承。
若同一個 Compose 專案內有多個服務,建議把代理相關變數集中在 .env,再於各服務引用,避免複製貼上造成一個服務更新、另一個仍指向舊 IP。對需要連回宿主機上其他服務(例如宿主跑著 Clash 以外的本機 API)的情境,記得搭配正確的主機名與埠映射,不要假設容器內的 localhost 等於宿主。
# Excerpt — adapt to Compose specification version you use
services:
app:
build:
context: .
args:
HTTP_PROXY: ${HTTP_PROXY}
HTTPS_PROXY: ${HTTPS_PROXY}
environment:
HTTP_PROXY: ${HTTP_PROXY}
HTTPS_PROXY: ${HTTPS_PROXY}
NO_PROXY: ${NO_PROXY}
BuildKit、docker buildx 與多階段建置的代理陷阱
啟用 BuildKit 後,建置過程可能與傳統引擎路徑不同;跨平台 buildx 還會涉及 builder 實例與遠端節點。若你只看到 docker build 最後失敗在某一層 RUN,請優先確認該層是否看得到代理環境變數,而不是只檢查宿主 shell。
多階段建置時,每一個 FROM 開啟新階段,ARG/ENV 傳遞規則要嚴格遵守 Dockerfile 語意;若前一階段設了代理、後一階段未帶入,仍可能在後段 RUN 失敗。對需要從內網 registry 拉基底映像的 pipeline,也別忘了在 CI 環境同樣設定 daemon 或 builder 層級代理,與本機桌面 Clash 分開思考。
實務排查順序建議:先確認宿主 Clash 日誌是否出現對應連線;若完全沒有,代表流量沒到代理;若有,但 TLS 仍失敗,再往下拆憑證與時間。這與一般 Clash 規則分流裡「先看命中策略、再換節點」的邏輯一致,可與YAML 規則並用。
bridge 與 host 網路:對 127.0.0.1 與 DNS 的影響
預設 bridge 模式下,容器有獨立網路命名空間,DNS 與路由表也與宿主不同;因此「在宿主能解析的 registry 主機名」在容器內若 DNS 設定不當,仍可能解析失敗。若你改為 host 網路,容器與宿主共用同一套網路介面,對本機服務的連線行為較接近直接在宿主執行,但埠衝突與隔離性也隨之下降,不應當成預設解法。
有些教學會建議在 Compose 內加 extra_hosts 將 host.docker.internal 指到宿主 IP,這在 Linux 上特別實用;但若 Clash 只監聽 127.0.0.1 而沒有對 docker0/區網開放,容器就算能解析主機名也連不上。請把「監聽位址」「防火牆」「允許區網」三件事一起檢查。
若你同時使用 TUN 模式的 Clash,請留意整機路由與 Docker 網橋是否出現意料之外的優先順序;異常時可先切回系統代理或縮小規則集,對照連線日誌縮小問題範圍。
NO_PROXY:私有 registry、內網與 localhost 例外
代理一開,最常踩雷的是內部服務也被送去海外節點。請為公司私有 registry、內部 Git、Kubernetes API、以及 localhost/127.0.0.1 設定適當的 NO_PROXY(或 no_proxy)清單。清單格式依工具略有差異,建議以逗號或空格分隔的寫法逐一套用驗證。
若 Clash 規則裡對特定網段設了直連,但 Docker 卻仍走代理,通常是兩套設定沒對齊:一邊在客戶端規則層,另一邊在 Docker 環境變數層。把它們當成兩條平行線維護,能省下大量「以為有代理、其實沒走到」的除錯時間。
常見問題(FAQ)
為什麼瀏覽器能開 Docker Hub,終端機 docker pull 卻逾時?
瀏覽器會套用系統代理或擴充套件;真正執行 pull 的是 Docker 引擎。若未對 dockerd/建置環境設定代理,或代理位址在容器內無法連線,就會出現落差。
HTTP_PROXY 要設在宿主 shell,還是寫進 docker-compose.yml?
讓引擎拉映像應以服務管理或官方建議方式設定在 dockerd;Compose 則分別處理 build 與 runtime。僅 export 在互動式 shell,常常無法涵蓋引擎層級。
為什麼代理要填宿主 IP 而不是 127.0.0.1?
在 bridge 容器內,127.0.0.1 指向容器本身;需使用宿主可路由位址或 host.docker.internal(視環境而定)。
bridge 與 host 網路對代理有什麼影響?
bridge 需透過宿主 IP 或額外主機名對接宿主服務;host 與宿主共用網路堆疊但隔離與衝突風險不同,應分情境測試。
寫在最後
2026 年容器與映像建置仍是後端與 AI 工程日常;Clash 這類支援細緻規則與連線可觀測性的客戶端,能幫你把「跨境 registry、TLS、與內網例外」拆開處理。相較只堆更多節點,把 Docker 引擎、BuildKit 與 Compose 服務各自對齊代理語意,往往更快終結反覆逾時。若你希望先把訂閱與圖形介面管理好用,再專心寫 Dockerfile 與 Compose,建議從本站安裝包取得一致的體驗與更新節奏:
需要釐清規則分流與策略組設計時,可繼續閱讀YAML 規則分流教學;開源授權與上游專案資訊請以各客戶端官方說明為準。更多主題歡迎瀏覽技術專欄首頁。