弊社ではPHPによる開発で使用するIDEはPhpStormが標準となっています。
PhpStormも今どきの流行かdevcontainer対応されており、されているからには使いたいと思うのですが、欲しい機能が無かったり期待した通り動かなかったりということが稀によくあるため、使う側が気を使って工夫してあげる必要があります。
今回はその中の1つ
「WSL上に配置したファイル(プロジェクト)からのdevcontainer起動が正常に行えない」
という問題の回避方法が分かったので共有しておこうという趣旨です。
公式に対して報告されている不具合としてはこれにあたると思われます。
https://youtrack.jetbrains.com/issue/IJPL-65925
そろそろ直りそうな気配もあり、直ったらこの記事も不要になるのですが、それならそれで喜ばしいということで。
(現時点の最新バージョン2025.1では直っていない)
結論から書きますと「devcontainer.jsonでworkspaceMountを指定する」となります。
		
		
			
			
			
			
				
					
				|  | "workspaceMount": "type=bind,source=../,target=/var/workspace" | 
				
			 
		 
以下は結論に至る経緯です。
Jetbrains devcontainerでは以下の2通りの方法でdevcontainerを作ることが出来ます。
- ローカルプロジェクトから作成
- ホスト機に置かれたファイル(devcontainer.json)を使って起動。
 
- VCSプロジェクトから作成
- VCS(Git)に置かれたファイル(devcontainer.json)を使って起動。
- 実際の動作としては、使うファイルだけテンポラリディレクトリにcloneされてそこから起動される
 
- 起動時にDocker volumeが作成され、そこにIDEが開く対象としてGit cloneされる
 
ただ、現状「VCSプロジェクトから作成」が以下のような点により使いにくいです。
- Git cloneした内容がホスト機から直接見られない
- HTMLやMicrosfot officeのドキュメントを入れてたりすると見られない
 
- Git cloneした内容が複数のコンテナで共有できない
- 専用のランダムな名前のDocker volumeが作成されるので、予めcompose.ymlで指定しておくということが出来ない
- 静的なファイルだけWebサーバのコンテナで配布するとか、Javascriptは別コンテナのnodeで動かすといったことが出来ない
 
- Dockerで使用する設定ファイルをBind mountしにくい
- devcontainerが使用するファイルだけテンポラリディレクトリにcloneされるのでローカルで一時的に変更を試すといったことが出来ない
 
であれば「ローカルプロジェクトから作成」を使おうという話になりますが、この時「ローカルプロジェクト」がWSLに置いてあると正常に動作しないという問題にあたるわけです。(Windowsに置いてあれば問題なく動作するが、Dockerのディスクアクセス速度の都合でWSL上に置きたい)
状況を確認するために、以下のファイルを使用します。
\\wsl.localhost\Ubuntu\home\kurosawa\devtest.devcontainer\devtestdevcontainer.json
		
		
			
			
			
			
				
					
				|  | {     "name": "devcontainer test",     "dockerComposeFile": "compose.yml",     "service": "devcontainer" } | 
				
			 
		 
\\wsl.localhost\Ubuntu\home\kurosawa\devtest.devcontainer\compose.yml
		
		
			
			
			
			
				
					
				|  | services:   devcontainer:     image: php:8.4-cli-bookwarm     init: true     tty: true | 
				
			 
		 
このdevcontainer.jsonを使いdevcontainerを起動すると、IDEからディレクトリの中身(\\wsl.localhost\Ubuntu\home\kurosawa\devtest)が見えません。
Docker inspectでMountsを確認すると以下のようになっています。
		
		
			
			
			
			
				
					
				|  | {     "Type": "bind",     "Source": "//wsl.localhost/Ubuntu/home/kurosawa/devtest",     "Destination": "/IdeaProjects/devtest",     "Mode": "rw",     "RW": true,     "Propagation": "rprivate" } | 
				
			 
		 
Docker単体であればWSLに置いても特に問題なくマウント出来たはずなので確認します。
compose.ymlにvolumesを追加。
		
		
			
			
			
			
				
					
				|  | services:   devcontainer:     image: php:8.4-cli-bookworm     init: true     tty: true     volumes:       - ../:/IdeaProjects/devtest | 
				
			 
		 
docker composeを使って直接起動します。
		
		
			
			
			
			
				
					
				|  | docker compose --file \\wsl.localhost\Ubuntu\home\kurosawa\devtest\.devcontainer\compose.yml up | 
				
			 
		 
問題なくディレクトリ(\\wsl.localhost\Ubuntu\home\kurosawa\devtest)の中身が確認出来ます。
Docker inspectでMountsを確認すると以下のようになっています。
		
		
			
			
			
			
				
					
				|  | {     "Type": "bind",     "Source": "\\\\wsl.localhost\\Ubuntu\\home\\kurosawa\\devtest",     "Destination": "/IdeaProjects/devtest",     "Mode": "rw",     "RW": true,     "Propagation": "rprivate" } | 
				
			 
		 
これらを見るに以下のような状況と思われます。
- Dockerはスラッシュ区切りのパスがうまく認識できない
- Dockerはバックスラッシュ区切りのパスは正常に認識できる
- devcontainerから起動するとスラッシュ区切りのパスでマウントされる
compose.ymlを上記のまま、devcontainerから起動すればよいのでは?と思いましたが、スラッシュ区切りのパスでマウントされてしまいます。
		
		
			
			
			
			
				
					
				|  | {     "Type": "bind",     "Source": "//wsl.localhost/Ubuntu/home/kurosawa/devtest",     "Destination": "/IdeaProjects/devtest",     "Mode": "rw",     "RW": true,     "Propagation": "rprivate" } | 
				
			 
		 
それならばとcompose.ymlでバックスラッシュ区切りのフルパスを指定してみますが、相対パスで指定した場合と同じでスラッシュ区切りのパスでマウントされます。
		
		
			
			
			
			
				
					
				|  | services:   devcontainer:     image: php:8.4-cli-bookworm     init: true     tty: true     volumes:       - \\wsl.localhost\Ubuntu\home\kurosawa\devtest:/IdeaProjects/devtest | 
				
			 
		 
では、バックスラッシュ区切りのパスにするにはどうしたらいいかというと、devcontainer.jsonのworkspaceMountで指定します。
		
		
			
			
			
			
				
					
				|  | {     "name": "devcontainer test",     "dockerComposeFile": "compose.yml",     "service": "devcontainer",     "workspaceMount": "type=bind,source=\\\\wsl.localhost\\Ubuntu\\home\\kurosawa\\devtest,target=/IdeaProjects/devtest" } | 
				
			 
		 
compose.ymlのvolumesは不要なので削除します。(書いてもdevcontainer.jsonの設定で上書きされる)
workspaceMountで指定すると、バックスラッシュのパスでマウントされます。
		
		
			
			
			
			
				
					
				|  | {     "Type": "bind",     "Source": "\\\\wsl.localhost\\Ubuntu\\home\\kurosawa\\devtest",     "Destination": "/IdeaProjects/devtest",     "Mode": "rw",     "RW": true,     "Propagation": "rprivate" } | 
				
			 
		 
workspaceMountは相対パスで指定しても大丈夫です。
devcontainer.jsonを共有することを考えるとこちらの方がよいでしょう。
マウント先のパスを変更することも出来ます。
		
		
			
			
			
			
				
					
				|  | {     "name": "devcontainer test",     "dockerComposeFile": "compose.yml",     "service": "devcontainer",     "workspaceMount": "type=bind,source=../,target=/var/workspace" } | 
				
			 
		 
		
		
			
			
			
			
				
					
				|  | {     "Type": "bind",     "Source": "\\\\wsl.localhost\\Ubuntu\\home\\kurosawa\\devtest",     "Destination": "/var/workspace",     "Mode": "rw",     "RW": true,     "Propagation": "rprivate" } | 
				
			 
		 
devcontainerは指定したcompose.ymlをそのまま使うわけではなく、devcontainerの動作用に改変して使用します。
挙動からの推測になりますがその改変処理時に、compose.ymlのvolumesに書いたパスはdevcontainerが一旦解釈して、結果スラッシュ区切りのパスになりdevcontainer.jsonのworkspaceMountはdevcontainerが解釈せずそのまま使われる。ということなのだと思います。