WSL/Docker CEのPHPコンテナとホスト側IDEのリモートデバッグ

WSLのDocker CEで動くPHP/Laravelコンテナをリモートデバッグしようとしたが、環境条件が違って一筋縄ではいかず苦労したので忘れないようメモ。

環境条件

  • ホストWindows11のIDE(PhpStorm)でPHPソース(Laravel)を編集
  • Laravelをローカルで動かすためDockerを利用するが、
  • Docker Desktop は使わず、WSL2 の Ubuntu に Docker Engine (CE) をコマンドラインインストールしてdockerコンテナを実行する
  • そのPHPコンテナでXdebugを有効にしてリモートデバッグする

①host.docker.internal の設定

『php.iniに xdebug.client_host = host.docker.internalと書けばDockerコンテナからホストに接続できる』とだけ書かれている記事が多いけど、それは Docker Desktop の話だったもよう。

Docker CE の場合はそれに加えてcompose.ymlに次の設定を書く必要がある。

services:
    php-apache:
        extra_hosts:
            - "host.docker.internal:host-gateway"

これでコンテナ内ではhost.docker.internalがホストに接続するための名前になるらしい。

リモートデバッグは、IDE(PhpStorm)が9003番ポートで待ち受け、コンテナ(Webサーバー)のPHPがIDEの9003番ポートに接続することで実現される。

コンテナにログインして telnet でホストの9003番ポートに接続できればリモートデバッグ可能なはず。

# telnet host.docker.internal 9003

しかし残念ながら今回の場合は接続できない。

なぜならhost.docker.internalはWSL(Ubuntu)であり、WSLの下にいるWindowsではない。
Windows上でPhpStormが9003番ポートで待ち受けていても、そこには届かないのだ。

WSL/Ubuntuから見たWindowsホスト名は$(hostname).localらしい。
WSL上でping $(hostname).localなどとするとIPアドレスがわかる。

そのWindowsホストのIPアドレスの9003番にコンテナ内から接続すればPhpStormに接続できる。

# telnet WindowsホストIPアドレス 9003

ということで、php.ini のxdebug.client_hostにはこのIPアドレスを書く。
これでリモートデバッグ可能となる。めでたしめでたし。

ではなく、これをできれば自動的にやりたい。いちいちIPを確認してphp.iniを編集するのではなく・・

しかし自動といっても、9003番ポートで待つIDEがDockerコンテナの直接のホストにいるのか?それともさらに上のホストにいる(今回の場合)のか?は、開発者の環境依存なのでわからない。自動判定は厳しい気がする。。。

仕方ないので「何かひと作業すればOK」くらいを目指す。

②リモートデバッグ通信をWSLからWindowsホストに転送

今回のように、IDEがDockerコンテナから見たホストのさらにホストのWindowsで動いてる場合は、WSL/Ubuntuで9003番ポートの通信をホストWindowsの9003番に転送(プロキシ)したらいいのではないか。

指定ポートの通信をリレー(プロキシ)してくれるsocatという便利なコマンドがあるらしい。
ということで次のようなシェルスクリプトをWSL/Ubuntu上で実行する。

#!/bin/bash

PHP_DEBUG_PORT=9003

DOCKER_IP=$(ip addr show docker0 | grep 'inet ' | awk '{print $2}' | cut -d/ -f1)

HOST_IP=$(dig +short "$(hostname).local" | head -n 1)

cmd="socat TCP-LISTEN:$PHP_DEBUG_PORT,bind=$DOCKER_IP,reuseaddr,fork TCP:$HOST_IP:$PHP_DEBUG_PORT"

echo $cmd
$cmd

これでDockerコンテナのPHPからhost.docker.internalに接続するとWindowsの9003番ポートに届くので無事リモートデバッグできるようになる。

うーんDocker Desktopならこんなことでは引っかからないのかな・・
host.docker.internal がWindows側にまで届くようにうまいことやってくれるのだろうか