開發與 AI · Clash 技術部落格

VS Code Dev Containers 怎麼走 Clash?

Dev Container 建置、容器內 Git 與擴展下載可能由不同處理程式發起。依據發生錯誤位置檢查主機代理位址、建置變數或容器環境,設定 NO_PROXY 保留內部網路存取,並分別驗證下載與執行。

  • VS Code
  • Dev Containers
  • Docker
  • 代理
本文目錄

宿主機瀏覽器能上網,開發容器卻可能卡在拉取映像、安裝相依套件或下載擴充功能。把代理寫進容器設定後仍失敗,常是修改的位置沒有對應實際發出請求的程式。找出失敗命令在哪裡執行,再檢查它能否連線宿主機代理,同時讓資料庫等內部服務繼續直連。

Dev Containers 卡在哪一步:拉鏡像、建置還是容器內下載?

記錄失敗命令和執行位置(Dev Containers 輸出或容器終端),區分拉取基礎映像、執行 Dockerfile、容器內 Git 或遠端擴充功能下載。VS Code 的這個功能把開發環境放進容器;請求由不同程序發出,應依下表為實際失敗的位置設定代理。

不同下載動作的代理設定

失敗動作實際發請求的元件設定入口
docker pull / 拉基礎映像檔Docker daemonDocker Desktop 或 daemon 代理
Dockerfile RUN apt/npm映像檔建置階段build args 或 BuildKit 設定
容器內 curl/Git/pip正在執行的容器containerEnv、remoteEnv 或工具設定
VS Code 遠端擴展下載本機 VS Code 與容器端服務本機與容器網路設定、擴充功能本身的設定

錯誤出現在「Starting Dev Container」之前,通常不能靠 .devcontainer/devcontainer.json 中的 containerEnv 修復,因為容器尚未執行。先記下失敗命令和 VS Code 輸出面板中的階段。

Dev Containers 的四段網路
  1. VS Code 主機負責拉取擴展與啟動 Docker
  2. Docker 建置階段讀取 build args 或 Docker 代理設定
  3. 執行中的容器讀取 containerEnv
  4. 遠端擴展與終端讀取 remoteEnv 或工具自己的設定

確認下載失敗發生在宿主機、建置階段還是執行中的容器,再為對應環境設定代理;三者不會自動共用環境變數。

容器裡的 127.0.0.1 不是宿主機

Clash 在主機上執行時,容器中的 http://127.0.0.1:7890 指向容器自己。Windows 與 macOS 的 Docker Desktop 提供 host.docker.internal;Linux Docker 可透過 host-gateway 對應同名位址。

Clash 若只監聽主機的回送位址,容器仍可能無法連線。開啟允許區域網路或讓 mixed-port 監聽容器可連線的介面時,也要用系統防火牆限制來源;不應把 7890 直接暴露到網際網路。

下方前半段在已執行的容器終端機執行。最後的 docker run 只用於 Linux 主機終端機,會建立暫時測試容器;不要在開發容器裡重複執行。兩處 7890 都要換成用戶端實際連接埠。

先從容器驗證連接埠,CLASH_PORT 按用戶端實際 mixed-port 修改
CLASH_HOST=host.docker.internal
CLASH_PORT=7890
getent hosts "$CLASH_HOST" || true
curl -v -x "http://$CLASH_HOST:$CLASH_PORT" https://www.example.com/

# Linux Docker 临时测试
docker run --rm --add-host=host.docker.internal:host-gateway curlimages/curl:latest -v -x http://host.docker.internal:7890 https://www.example.com/

Linux 宿主機把 host-gateway 寫進 devcontainer.json

.devcontainer/devcontainer.json 片段
{
  "runArgs": [
    "--add-host=host.docker.internal:host-gateway"
  ]
}

修改後執行 Dev Containers: Rebuild Container,舊容器不會自動獲得新的 hosts 映射。

Docker Compose 專案則在服務下使用 extra_hosts: ["host.docker.internal:host-gateway"]。不要同時在兩個地方重複新增。

容器能存取宿主機連接埠後,設定 containerEnv 與 remoteEnv

容器內的 curl 透過代理收到 HTTP 回應後,再設定工具環境。下例適用 Dockerfile 或 image 專案,同時提供大小寫變數以相容不同工具;連接埠依實際 mixed-port 修改。Compose 專案將容器變數寫入服務的 environment,只在 devcontainer.json 保留 remoteEnv。

專案級 devcontainer.json 示例
{
  "containerEnv": {
    "HTTP_PROXY": "http://host.docker.internal:7890",
    "HTTPS_PROXY": "http://host.docker.internal:7890",
    "NO_PROXY": "localhost,127.0.0.1,::1,host.docker.internal",
    "http_proxy": "http://host.docker.internal:7890",
    "https_proxy": "http://host.docker.internal:7890",
    "no_proxy": "localhost,127.0.0.1,::1,host.docker.internal"
  },
  "remoteEnv": {
    "HTTP_PROXY": "${containerEnv:HTTP_PROXY}",
    "HTTPS_PROXY": "${containerEnv:HTTPS_PROXY}",
    "NO_PROXY": "${containerEnv:NO_PROXY}",
    "http_proxy": "${containerEnv:http_proxy}",
    "https_proxy": "${containerEnv:https_proxy}",
    "no_proxy": "${containerEnv:no_proxy}"
  }
}

HTTPS_PROXY 使用 http:// 開頭通常是正確的:它表示透過 HTTP CONNECT 代理存取 HTTPS,並不是把代理連接埠本身變成 HTTPS。NO_PROXY 還應加入 Compose 服務名、公司內部網路與本機開發網域,否則容器存取資料庫或 API 時可能繞去 Clash。

儲存後 Rebuild Container,再在新終端執行 env | grep -i proxy。已有終端處理程式不會自動重新整理 remoteEnv。

映像檔拉取與 Dockerfile 建置要單獨設定

映像檔拉取在 Docker Desktop 或建置器/daemon 端設定;Dockerfile 的 RUN 則使用 build.args。Linux 上前面的 runArgs 對應只影響執行中的容器,建置階段還須下方的 build.options 對應。不要把個人代理或認證資訊寫成 ENV 留在映像檔中。

devcontainer 建置參數片段,7890 按實際 mixed-port 修改
{
  "build": {
    "dockerfile": "Dockerfile",
    "args": {
      "HTTP_PROXY": "http://host.docker.internal:7890",
      "HTTPS_PROXY": "http://host.docker.internal:7890",
      "NO_PROXY": "localhost,127.0.0.1",
      "http_proxy": "http://host.docker.internal:7890",
      "https_proxy": "http://host.docker.internal:7890",
      "no_proxy": "localhost,127.0.0.1"
    },
    "options": [
      "--add-host=host.docker.internal:host-gateway"
    ]
  }
}

curl 成功後,Git、apt 和擴展仍要各測一次

在容器終端確認 HTTP_PROXY、HTTPS_PROXY 和 NO_PROXY 已經出現,然後分別執行 Git 和套件管理員命令。Git 通常會讀取環境變數;若它仍指向舊位址,再檢查 Git 自己的設定。

透過擴充功能面板或 devcontainer.json 安裝擴充功能時,本機與容器端 VS Code Server 都須能存取 Marketplace 和下載 CDN。修改 remoteEnv 後重新連線並開啟新終端機,從輸出面板確認實際失敗的請求;不必只為 remoteEnv 重建整個容器。

容器內檢查 Git;REPO_URL 可換成專案自己的公開儲存庫
REPO_URL='https://github.com/octocat/Hello-World.git'
env | grep -i '_proxy'
git config --show-origin --get-regexp 'http.*proxy' || true
git ls-remote "$REPO_URL"

# 单次指定代理,不修改已有全局设置
git -c http.proxy=http://host.docker.internal:7890 ls-remote "$REPO_URL"

curl 成功,Git 仍連線舊連接埠

執行 git config --show-origin --get-regexp 'http.*proxy',清理使用者或儲存庫殘留。

apt update 失敗

查看實際儲存庫網域、憑證與 /etc/apt/apt.conf.d 中的獨立代理。

專案能執行,遠端擴展裝不上

在 Dev Containers 日誌中找 marketplace 請求,確認遠端服務繼承了 remoteEnv。

容器能聯網,本機資料庫連線失敗

把資料庫服務名和私有網段加入 NO_PROXY。

Rebuild 階段仍失敗

回到 daemon 拉映像檔或 Dockerfile build args,不繼續改執行容器。

共享儲存庫不要寫死個人代理

團隊專案可以在 devcontainer.json 使用 ${localEnv:HTTP_PROXY} 讀取開發者本機環境,或提供不含實際位址的 .env.example。

README 裡寫清楚 HTTP_PROXY 應是完整位址,例如 http://host.docker.internal:7890。提交前檢查代理 URL 中沒有使用者名稱、密碼和 token。

專案共享前分別驗證 docker pull、Rebuild Container、容器內 Git/套件管理員和本機服務存取。失敗時儲存對應命令和日誌,按照本文的設定入口檢查,不必同時修改所有代理設定。

參考資料