2025年12月6日 星期六

在 Windows 環境使用 Docker 指令控制 WSL2 中的 Docker (不是 Docker Desktop) 的設定方式

 Windows 的環境,
並且已在 WSL2 上安裝了 Docker 情況下 (不是 Docker Desktop),

我們一般可以在 WSL 2 上直接執行 Docker 命令,例如 docker ps, docker version 等。

但如果我們想在 WSL 2 外部,也就是使用 CMD 或 PowerShell 來執行 Docker 命令的話,
就必須做些額外設定才能達成。

我們的目標有兩個:

  1. 讓 WSL 2 中的 Docker daemon 能將 Port 暴露給外部。
  2. 在 Windows 下要有 Docker CLI 讓 Windows 能使用 Docker 命令,並且要告訴 Docker CLI 要把命令傳給哪一個 Port。

為了讓 Docker daemon 能夠被 Windows 存取,首先我們要先進入 WSL 2 中,
找到或自行建立 /etc/docker/daemon.json,內容改為:

{
   "hosts": ["tcp://127.0.0.1:2375", "unix:///var/run/docker.sock"]
} 

/etc/docker/daemon.json 設定了 Docker daemon 的連接方式,也就是接受命令的方式,
unix:///var/run/docker.sock 是原本在 WSL 2 中 Docker 指令直接連接的目標。
tcp://127.0.0.1:2375 則是我們多加的,讓 WSL 2 外部能以 tcp localhost (127.0.0.1) + 2375 Port 的方式連接 Docker daemon。

將 Docker daemon 監聽 2375 Port,接著我們要以下指令重新啟動 Docker daemon service :

#service - 較舊的用法 (在現在其實也是去呼叫 systemctl)

service docker restart

#systemctl - 較新的用法

systemctl restart docker

如果設定了 daemon.json 後發現 docker service 無法正常重啟或啟動,
通常是因為 DOcker 本身的 /lib/systemd/system/docker.service 中的
ExecStart 指令設定跟 /etc/docker/daemon.json 互相衝突。

在 /lib/systemd/system/docker.service 中有一句像這樣的設定 (Docker version 29.0.2)

ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

其中 -H fd:// 是Systemd 的 Socket Activation (套接字激活) 功能,
關於 Socket Activation 可以參考:

  1. 一次socket activation的探索体验-CSDN博客
  2. Systemd 的 socket activation 机制 | Zwlin's Blog
簡單的來說就是 systemd 預先建立了 socket 監聽請求,讓 Docker 不用自行建立 socket ,而是使用 systemd 建立的 socket,請求不會直接傳給 Docker daemon+, 而是被 systemd 的 socket 攔截了下來,當 socket 收到請求時才會去啟動 Docker daemon (如果 Docker daemon 還沒被啟動的話),類似 lazy 啟動的感覺。

但我們現在不想要 systemd socket 攔截我們的請求,而是想要能直接傳送請求給我們在
/etc/docker/daemon.json 中的那些監聽設定,所以我們必須要重新覆蓋掉
/lib/systemd/system/docker.service  中的 ExecStart 設定。

我們需要去建立

/etc/systemd/system/docker.service.d/override.conf

內容如下:

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd

這裡注意到我們須要寫兩行的 ExecStart=,第一行用來清除
/lib/systemd/system/docker.service
的 ExecStart 內容,第二行用來定設定我們要的自定義內容。

之後再執行以下指令 :

讓 systemd 重新載入 /etc/systemd/system/docker.service.d/override.conf 的設定

systemctl daemon-reload

重啟 Docker daemon

systemctl restart docker

sudo systemctl daemon-reload

sudo systemctl restart docker

以上都做完以後,從 Windows 就可以用 127.0.0.1:2375 存取 Docker daemon 了,
我們可以在 WSL 2 中用以下命令檢查 2375 Port 有沒有被監聽:

netstat -nl | grep 2375

ss -lntp | grep 2375

再來要注意的是有時 Windows 可能會有保留某些範圍的 Port 做特別用途而不給使用,
就算 Port 有被監聽還是無法正常讓 Windows 跟 Docker daemon 連接。

這時可以用以下指令來檢查:

netsh interface ipv4 show excludedportrange protocol=tcp

例如指令結果如果是如下:

Protocol tcp Port Exclusion Ranges

Start Port    End Port
----------    --------
      2211        2310
      2311        2410
      2511        2610
      2611        2710
      5357        5357
      7749        7848
     10824       10923
     14846       14945
     50000       50059     *

* - Administered port exclusions.

可以看到 2375 Port 是在 2311 ~ 2410 中被系統保留不能使用 ,
所以這時我們就要改設定其他 Port,不能用 2375。

可參考 Port 2375 not listening · Issue #3546 · docker/for-win

最後,雖然 Windows 也可連上 Docker daemon 了,除非你想直接底層用 Socket 連接 (例如 Testcontainers 這個 Java Library 可以做到),
不然還是裝上 Docker CLI 讓其提供方便的 Docker 指令給 Windows 使用。

我們先去下載相應 Docker 版本的 Docker CLI for Windows,下載網址如下:

https://download.docker.com/win/static/stable/x86_64/

下載後會是一個 zip 檔,解壓縮後得到一個資料夾,裡面有 docker.exe, dockerd.exe, 等檔案。

先設定環境變數的 Path 到資料夾路徑讓我們可以方便使用 Docker CLI 指令後,
還需要設定 DOCKER_HOST 環境變數讓 Docker CLI 知道要去哪裡連接 Docker daemon,
我們設定如下環境變數:

DOCKER_HOST : tcp://127.0.0.1:2375

然後再重新打開 PowerShell 或 cmd 試著執行 Docker 指令,就可以成功連上 Docker daemon 並執行 Docker 指令了,

例如執行 docker version:

docker version

應該就可以成功看到 Docker 的 version 資訊了。

參考資料:

  1. How to run tests with TestContainers in WSL2 without Docker Desktop
  2. 如何移除 Docker Desktop 並在 Windows 與 WSL 2 改安裝 Docker Engine | The Will Will Web
  3. 一次socket activation的探索体验-CSDN博客
  4. Systemd 的 socket activation 机制 | Zwlin's Blog
  5. service - Unable to start docker after configuring hosts in daemon.json - Stack Overflow
  6. Port 2375 not listening · Issue #3546 · docker/for-win