🐳

Docker + WSLg + devcontainer.json によるGUIアプリについて

commits12 min read

WSLg + DockerでGUIアプリ作成という記事が見当たらなかったので需要があるかなと思い、記事にしました。(他にもいくつか理由はありますが…)

まとめ

  • XサーバーなしでDockerを用いたGUIを表示したければ/mnt/wslgディレクトリのマウント、その他WSLの環境変数とボリュームをマウントする。

  • Remote DevelopmentでWSLのボリュームをマウントをしたいのなら、WSL内のディレクトリにプロジェクトを作る。

  • Remote Development拡張機能を使わず、手動でコンテナを作成し、その後アタッチしても問題なく動作するが、その場合拡張機能のインストールは手動。(地味にかなり手間。extensions.jsonがあっても機能せず)

この記事で作成されたテンプレート

  • 最終的にはこのようになります。タスクトレイをご覧いただくとXサーバーを使わずGUiアプリが起動できているのが確認できます。

  • Unlicenseなのでお好きにどうぞ。

gui-sample

  • GUIの表示まで動作確認済み

https://github.com/SARDONYX-sard/docker-wslg-gui-template

経緯

友人のPythonのGUIアプリ開発でのお困りごとを解決するため、ローカルに入ってるバージョンとは違ったpythonを使う必要があったのですが、ちょっとした事情でDockerに頼ることとしました。

ところが「Dockerコンテナ上でのGUI表示」というのは予想以上に苦戦しました。

X.Org Serverを使ったGUIアプリをWindows上で表示」というのは試したことがあるのですが、よりお手軽な「WSLgを使ったGUIの起動」がしたくなったのです。

情報収集とその結果

1. 指定された4つの環境変数と2つのボリュームのマウントを行う必要がある

  • 「WSLg Docker」と検索したところ、redditで以下の議論を目にしました。

Can I use the new WSLg with Docker?WSL2

These are the env variables and volumes needed for full support for wayland,
xwayland and Pulseaudio inside the docker containers:

-e DISPLAY

-e WAYLAND_DISPLAY

-e XDG_RUNTIME_DIR

-e PULSE_SERVER

-v /tmp/.X11-unix:/tmp/.X11-unix

-v /mnt/wslg:/mnt/wslg

2. Docker-composeを使うとより簡単

  • たった1つのDockerfileの起動にはdocker-compose.ymlは不必要だと思っておりましたが、利便性の高さから使ったほうがよさそうです。

Docker内でPython-GUIアプリを作成する(PySimpleGUI)

3 VS Codeの拡張機能の「Remote Development」を使ってお手軽にコンテナを立ち上げられる

  • devcontainer.jsonによってVS Codeから2回ボタンを選択すれば自動でコンテナが立ち上がるようです。

既存のDocker開発環境をVS CodeのRemote Developmentで開発できるようにしてみた

準備するもの

上記の情報から以下の条件が導けます。

必須

  • Windows11(WSLgのため)
  • WSLgが使用可能な状態のWSL(DockerのGUIをwindowsに表示させるため必要。こちらを参照)
  • Docker Desktop for Windows(仮想コンテナで任意の環境を構築)

任意

実装

今回はPythonでGUIアプリを作成していますがDockerfileを変えれば同じようにできるかと思います。

Pythonプロジェクトの設定はこちらを参考に以下のように構成しました。

ディレクトリ構成

docker-wslg-gui-template
├── .devcontainer # 拡張機能Remote Developmentの設定ファイル
|  └── devcontainer.json
├── .github
|  ├── dependabot.yml
|  └── workflows
|     └── ci.yml # サンプルCI/CDの設定ファイル
├── docker
|  ├── .env.wslg # WSLgのための設定ファイル
|  ├── Dockerfile
|  └── docker-compose.yml
├── docs
|  ├── i18n
|  |  └── jp
|  └── images
|     └── gui-sample.png
├── scripts
|  └── set-wslg.sh # .envファイルをセットアップし、コンテナの設定を表示するスクリプト
├── src
|  ├── main.py # GUIサンプル(コードは公式からの引用)
|  └── utils
|     ├── __init__.py
|     └── operator.py # CIを通すためだけに存在するサンプルファイル
├── tests
|  ├── __init__.py
|  └── test_operator.py # テスト用のサンプルファイル
├── .editorconfig
├── .gitignore
├── LICENSE
├── Makefile # compose、.env.wslgのセットアップコマンド管理
├── poetry.lock
├── pyproject.toml
└── README.md

各ファイルの解説

Docker関連についてのみ解説します。 他のファイルはテンプレートをほぼそのまま使用しているで特筆すべき点はありません。

docker-compose.yml

# https://dev.classmethod.jp/articles/add-vs-code-remote-development-settings-to-existing-docker-environment/

version: "3.7"
services:
  python-gui:
    build:
      dockerfile: ./Dockerfile
    env_file:
      - ./.env.wslg # 4つの環境変数は別ファイルで読み込ませる
    restart: always
    tty: true
    volumes: # 集めた情報の通り、ボリュームのマウントを行う
      - ../:/home/user/code
      - /mnt/wslg:/mnt/wslg
      - /tmp/.X11-unix:/tmp/.X11-unix

redditでの議論通りに指定の4つ環境変数の設定と指定の2ボリューム、自分のプロジェクトディレクトリのマウントを行います。

当初はWindows内のディレクトリにプロジェクトを作ってからRemote Containersを立ち上げたのですが、それだと正常にWSLのボリュームをマウントできず困りました。

試しにWSLのディレクトリを以下のようにvolumesプロパティに指定してみたのですがこれも失敗。

Microsoft.PowerShell.Core\FileSystem::\\wsl.localhost\Ubuntu\mnt\wslg

\\wsl.localhost\Ubuntu\mnt\wslg

WSL内のボリュームをWindows側から参照しようとする行為は、どうあっても失敗するようです。

困り果てていたところ、以下の記事を発見。

WSL2+VSCode Dev ContainerでFilesharingの警告

以下でWSLgのボリュームを正常にコンテナへマウント可能になりました。

  • WSL内のディレクトリにプロジェクトディレクトリを作る必要があること。
  • VS CodeでWSL内に入りReopen in Containerをクリック。

.env.wslg

  • docker-composer.ymlのenvironmentプロパティ内に直接値を書くか迷いましたが、他の環境変数の追加修正のしやすさからあえてファイルを作りました。
  • ここはお好みかと思います。
DISPLAY=:0
PULSE_SERVER=/mnt/wslg/PulseServer
WAYLAND_DISPLAY=wayland-0
XDG_RUNTIME_DIR=/mnt/wslg/runtime-di

devcontainer.json

集めた情報の3つ目をもとにdevcontainer.jsonを書いていきます。

devcontainer.json referenceを見ながら項目の詳細を見ていくとコンテナ作成時に自動実行されるフック処理を発見。

"onCreateCommand"プロパティに値poetry installと設定することで、依存関係のインストールをコンテナ作成時に自動で行えるようになりました。

// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.158.0/containers/docker-existing-docker-compose
// If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml.
{
  "dockerComposeFile": ["../docker/docker-compose.yml"],
  "extensions": [
    "almenon.arepl",
    "bungcip.better-toml",
    "dongli.python-preview",
    "donjayamanne.python-environment-manager",
    "donjayamanne.python-extension-pack",
    "esbenp.prettier-vscode",
    "kevinrose.vsc-python-indent",
    "mgesbert.python-path",
    "ms-ceintl.vscode-language-pack-ja",
    "ms-python.python",
    "ms-python.vscode-pylance",
    "mutantdino.resourcemonitor",
    "njpwerner.autodocstring",
    "njqdev.vscode-python-typehint",
    "oderwat.indent-rainbow",
    "spmeesseman.vscode-taskexplorer",
    "streetsidesoftware.code-spell-checker",
    "tamasfe.even-better-toml",
    "visualstudioexptteam.vscodeintellicode"
  ],
  "name": "wslg-gui-template",
  "onCreateCommand": "make install-dev", // コンテナ作成時に依存関係のインストールを行うようにする
  "remoteUser": "user", // To prevent poetry's path errors.
  "service": "python-gui", // docker-compose-ymlで名付けたservice名
  "settings": {
    "[python]": {
      "editor.defaultFormatter": "ms-python.python",
      "editor.formatOnType": false,
      "editor.tabSize": 4
    },
    "cSpell.words": [
      "autopep",
      "devcontainer",
      "mypy",
      "noprofile",
      "pycache",
      "pycodestyle",
      "pydocstyle",
      "pylint",
      "pysen",
      "wslg",
      "yapf"
    ],
    "editor.rulers": [80],
    "editor.tabCompletion": "on",
    "files.associations": {
      ".env": "dotenv",
      ".gitignore": "ignore",
      "*.json": "jsonc",
      "*.md": "markdown",
      "*.spec": "python"
    },
    "files.exclude": {
      ".mypy_cache": true,
      ".venv": true,
      "**/__pycache__": true,
      "**/.idea": true
    },
    "files.watcherExclude": {
      "**/__pycache__": true,
      "**/.git/objects/**": true,
      "**/.idea": true
    },
    "python.defaultInterpreterPath": "/usr/local/bin/python",
    "python.envFile": "${workspaceFolder}/.env",
    "python.formatting.provider": "black",
    "python.linting.enabled": true,
    "python.linting.flake8Enabled": true,
    "python.linting.lintOnSave": true,
    "python.linting.mypyEnabled": true,
    "python.linting.pycodestyleEnabled": true,
    "python.pythonPath": "${workspaceRoot}/.venv/Script/python"
  },
  "workspaceFolder": "/home/user/code"
}

Dockerfile

お手軽なサンプルとしたかったのでslim-busterを利用しましたが、こちらはイメージの軽量化のためいくつかの標準ライブラリが入っていません。

そして今回のサンプルで使っているPySimpleGUIが裏で標準ライブラリのTkinterを使っているようなので、そのままではGUIが動作しません。

そこでtk-devを別途インストールするよう指定します。
(ちなみに筆者が試しにtk-devなしでコンテナを立ち上げたところ、Tkinterがないといった文言のエラーが出力され、GUIが表示されませんでした。)

情報源:
python is not configured for tkinter #122

それともう1つ、poetryをinstallするときroot権限だとパスがうまく通らないようで、userを作る必要が出てきました。

FROM python:3.8.9-slim-buster

# hadolint ignore=DL3008
RUN apt-get update \
  && apt-get install -y curl git make sudo vim tk-dev --no-install-recommends \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

RUN adduser --disabled-password --gecos '' user \
  && echo 'user ALL=(root) NOPASSWD:ALL' > /etc/sudoers.d/user
USER user
WORKDIR /home/user/code

# hadolint ignore=DL3013
RUN pip install --no-cache-dir --upgrade pip \
  && pip install --no-cache-dir --upgrade setuptools

SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python -

ちなみにビルド後のイメージサイズは以下の通りになりました。
上がPython:3.8.9、下がPython:3.8.9-slim-busterを使って構成したイメージです。

image-size

コンテナの立ち上げ

Step 1: 以下のコマンドを実行します。

git clone https://github.com/SARDONYX-sard/docker-wslg-gui-template.git
cd docker-wslg-gui-template
code .

Step 2: 左下のWSL: <Linux name>(画像ではUbuntu)というアイコンをクリックします。

WSL-button

Step 3: 中央に現れたパレットからReopen in Containerというを項目を選択します。 (初回のビルドはかなり時間がかかります。)

remote-container

既知の問題

確認した問題は以下です。

  • Dockerfileで作ったユーザーではMakefileなどいくつかのファイルの編集権限がない

    =>Dockerfile内でユーザーを作らずにdevcontainer.jsonで適当なユーザー名を指定するだけで問題ないかなと試したのですが…うまくいかず。
    現状、Makefileなど権限エラーのものはコンテナの外で編集し解決しています。

  • RAMを圧迫し続け、永遠にBuildが終わらない場合あり => これはPCの再起動で解決しましたが…弥縫策でしかないという…

感想

「やっぱり需要がないから記事がないんじゃないのかな…?」と思いました。

意外と詰まったところが多く、3日ぐらい悩むことになりました。 ちなみにようやく環境を構築したと思ったら、友人はすでに問題を自己解決していました。
無念。ともあれその悲しみから記事にしようと思い立ったのですが。

最後にまとめとテンプレートを再掲して終わります。

まとめ(再掲)

  • XサーバーなしでDockerを用いたGUIを表示したければ/mnt/wslgディレクトリのマウント、その他WSLの環境変数とボリュームをマウントする。

  • Remote DevelopmentでWSLのボリュームをマウントをしたいのなら、WSL内のディレクトリにプロジェクトを作る。

  • Remote Development拡張機能を使わず、手動でコンテナを作成し、その後アタッチしても問題なく動作する。(その場合拡張機能のインストールは手動)

この記事で作成されたテンプレート(再掲)

  • GUIの表示まで動作確認済み

  • Unlicenseなのでお好きにどうぞ。

https://github.com/SARDONYX-sard/docker-wslg-gui-template

その他本稿のために参照したサイト

GitHubで編集を提案

Discussion

ログインするとコメントできます