はじめに
最近の WSL には、Docker Desktop なしでコンテナーを動かせる WSL Container(wslc.exe) が入ってきました(WSL 2.9.3 以降)。
「じゃあ Copilot CLI に wslc でコンテナー動かしてもらおう!」と思ったんですが、ここで壁にぶつかります。
LLM、WSL Container をまだ知らない。
docker run や docker ps なら完璧に答えてくれるのに、wslc となると「そんなコマンドないよ?」「wsl.exe のことですか?」みたいな反応になりがちです。まあ、新しい機能なので学習データに入ってないのは当然ですよね。
WSL Container のおかげで、WSL に Docker を入れなくてよくなった
これまで WSL でコンテナーを動かすには、次のどちらかが必要でした。
- Docker Desktop をインストールして WSL 連携を有効にする
- WSL ディストリビューション内に Docker Engine を入れて、デーモンの起動や権限(
dockerグループ)を管理する
WSL Container(wslc)なら、WSL 本体に同梱されているので、追加のインストールも常駐デーモンの面倒も不要です。wsl --update で WSL を最新にするだけで使えます。
実際、この記事の検証でも WSL 内に Docker は一切入れていません。alpine の pull や、Jupyter 入りイメージのビルド・実行まで、すべて wslc だけで完結しました。環境構築の手間が減るので、Copilot に任せるときも「まず Docker を入れて…」というステップが要らないのが地味にうれしいです。
発想:知らないなら教えてあげればいい
LLM は Docker コンテナーはよく知っています。wslc のサブコマンド体系もかなり Docker に似ています。ということは、差分だけ教えてあげれば動くはず。
そこで使うのが Agent Skill です。
Agent Skill は、SKILL.md に「こういうときはこうしてね」という手順書を書いておくと、Copilot が必要なときだけ読み込んでくれる仕組みです。再学習もファインチューニングもいりません。Markdown を 1 枚置くだけ。
作ったもの
リポジトリはこちらです。
構成はこれだけ。
.github/skills/wslc-containers/
├── SKILL.md # Copilot への手順書
└── scripts/wslc.sh # wslc.exe を探して実行するラッパー
ポイント 1:SKILL.md に「何者か」を書く
SKILL.md の先頭には frontmatter で名前と説明を書きます。この description が重要で、Copilot は これを見て「今このスキルを使うべきか」を判断します。
---
name: wslc-containers
description: Manage Linux containers on Windows using the built-in WSL container CLI (wslc.exe), without Docker Desktop. Use this when asked to build, run, list, inspect, stop, or otherwise operate containers via WSL/wslc from a Copilot CLI session running inside WSL.
allowed-tools: shell
---
「いつ使うか」まで書いておくのがコツです。
本文には、LLM が知らない情報を書いていきます。
-
wslc.exeは WSL 2.9.3 以降に同梱されている - Docker Desktop は不要
- WSL 内の bash から、Windows 側の
wslc.exeを呼び出す - 主要コマンドの一覧
wslc version
wslc image ls
wslc pull docker.io/library/alpine:latest
wslc build -t <tag> <path>
wslc run --rm -it <image> <cmd...>
wslc run -d --name <name> -p <hostPort>:<containerPort> <image>
wslc container ps
wslc exec <container> <cmd...>
wslc logs <container>
wslc stop <container>
wslc remove <container>
Docker を知っている LLM にとっては、この一覧だけで「あ、だいたい docker と同じ感覚ね」と理解してくれます。ただし ps が container ps だったり、rm が remove だったりと細かい違いはあるので、そこはちゃんと書いておきます。
ポイント 2:パスの罠はスクリプトで潰す
WSL から Windows の exe を呼ぶとき、地味にハマるのがパスです。
/mnt/c/Program Files/WSL/wslc.exe
スペースが入っているんですよね。LLM が生成したコマンドでクォートが漏れて失敗する、というのがありがちです。
そこで、探索と実行をラッパースクリプトにまとめました。
#!/usr/bin/env bash
set -euo pipefail
DEFAULT_PATH="/mnt/c/Program Files/WSL/wslc.exe"
find_wslc() {
if [ -x "$DEFAULT_PATH" ]; then
echo "$DEFAULT_PATH"
return 0
fi
# Program Files 配下を探す
local candidate
candidate=$(find /mnt/c/Program\ Files /mnt/c/Program\ Files\ \(x86\) \
-maxdepth 3 -iname 'wslc.exe' 2>/dev/null | head -n 1 || true)
if [ -n "${candidate:-}" ]; then
echo "$candidate"
return 0
fi
# 最後の手段:Windows 側の PATH に聞く
if command -v cmd.exe >/dev/null 2>&1; then
candidate=$(cmd.exe /c "where wslc.exe" 2>/dev/null | tr -d '\r' | head -n 1 || true)
if [ -n "${candidate:-}" ]; then
echo "$candidate"
return 0
fi
fi
return 1
}
WSLC_BIN="$(find_wslc || true)"
if [ -z "${WSLC_BIN:-}" ]; then
echo "Error: wslc.exe was not found." >&2
echo "Ensure WSL is updated (run 'wsl --update' on Windows, requires WSL >= 2.9.3)" >&2
exit 1
fi
exec "$WSLC_BIN" "$@"
SKILL.md には「パスを直書きせず、必ずこのラッパー経由で呼んでね」と書いておきます。LLM に判断させず、決定的にできるところはスクリプトに寄せるのが安定するコツだと感じました。
ポイント 3:安全面のガイドも書いておく
wslc run や wslc exec は、コンテナー内で任意のコードを動かせます。なので、SKILL.md には Copilot への注意事項も入れています。
- 見慣れないイメージや信頼できないレジストリは、実行前にユーザーへ確認する
- 名前・ポート・パスは使う前に検証する
-
removeやrmiの前にcontainer ps -aやimage lsで状態を確認する -
wslc.exeが見つからないときは、勝手に回避せずエラーを報告してwsl --updateを案内する
「知らないツールを触らせる」ときほど、ガードレールも一緒に渡しておくと安心です。
使ってみる
スキルは .github/skills/ 配下に置くと Copilot が見つけてくれます。個人用なら ~/.copilot/skills/ でも OK です。
git clone https://github.com/nahisaho/wslc-containers-skill.git
cp -r wslc-containers-skill/.github/skills/wslc-containers <your-project>/.github/skills/
あとは普通に話しかけるだけです。
wslc で alpine を pull して uname -a を実行して
このディレクトリの Dockerfile をビルドして、8080 番ポートで起動して
動いてるコンテナー一覧を出して、web のログを見せて
「wslc って何?」と聞き返されることなく、ちゃんとラッパー経由で wslc を叩いてくれます。
応用:自然言語で Jupyter + Jupyter MCP 入りコンテナーを作ってみた
基本操作ができたので、もう少し実用的なことを頼んでみました。
alpine に jupyter, jupyter-mcp を搭載したイメージを作成して
Copilot は Skill に従って、Dockerfile の作成からラッパー経由の wslc build までを進めてくれました。生成された Dockerfile はこちらです(リポジトリの examples/alpine-jupyter-mcp/Dockerfile)。
FROM docker.io/library/alpine:latest
RUN apk add --no-cache python3 py3-pip gcc musl-dev python3-dev libffi-dev \
&& python3 -m venv /opt/venv \
&& /opt/venv/bin/pip install --no-cache-dir jupyterlab jupyter-mcp-server \
&& apk del gcc musl-dev python3-dev libffi-dev
ENV PATH="/opt/venv/bin:$PATH"
RUN adduser -D jupyter
USER jupyter
WORKDIR /home/jupyter/work
EXPOSE 8888
CMD ["jupyter", "lab", "--ip=0.0.0.0", "--port=8888", "--no-browser"]
感心したポイントは次のとおりです。
- ビルド用パッケージ(gcc など)を同じ
RUN内でapk delして、イメージを小さくしている - venv に閉じ込めて、Alpine のシステム Python を汚さない
- 非 root ユーザー(
jupyter)で動かす
実際に実行されたコマンドは、ラッパー経由のこの 1 本です。
bash .github/skills/wslc-containers/scripts/wslc.sh build -t alpine-jupyter-mcp:latest .
ビルドには数分かかりました(Alpine の musl 環境では一部パッケージのビルドが入るため)。完了後、コンテナー内で次のように確認できました。
-
jupyter --versionが動く(IPython 9.x / ipykernel 7.x) -
jupyter-mcp-server --helpが動く(jupyter_mcp_server2.2.3)
起動は自然言語でも、コマンドでもできます。
bash .github/skills/wslc-containers/scripts/wslc.sh run -d --name jlab -p 8888:8888 alpine-jupyter-mcp:latest
MCP 接続テスト
その後、実際に JupyterLab と jupyter-mcp-server を起動して、MCP クライアントから接続できるか確認しました。すべて 1 つのコンテナー内で完結させています。
# JupyterLab を起動(トークンはテスト用)
wslc run -d --name jlab-test alpine-jupyter-mcp:latest \
jupyter lab --ip=127.0.0.1 --port=8888 --no-browser --IdentityProvider.token=testtoken
# コンテナー内で MCP サーバーを streamable-http で起動
jupyter-mcp-server --transport streamable-http --port 4040 --mcp-token mcptoken \
--code-sandbox-url http://127.0.0.1:8888 --code-sandbox-token testtoken
クライアントは、コンテナーに入っている mcp パッケージで書いた簡単なスクリプト(examples/alpine-jupyter-mcp/mcp_smoke_test.py)を wslc exec -i の標準入力から流し込んで実行しました。
結果は次のとおりです。
- ツール一覧を 18 個取得できた(
list_kernels、execute_code、use_notebook、insert_execute_code_cellなど) -
list_kernelsでpython3(ipykernel)がidleで見えた -
execute_codeでplatform.platform()を実行し、Linux-6.18.40.1-microsoft-standard-WSL2-x86_64-with-musl1 3.14.8が返ってきた
つまり MCP 経由でコンテナー内の Jupyter カーネルのコードを実行できることまで確認できました。
ハマりどころが 2 つありました。
-
streamable-httpは認証が必須で、--mcp-token(または--insecure-mcp-noauth)を付けないと起動時にエラーになる - 入っている
mcpは 2.x で、クライアント API がstreamablehttp_clientからstreamable_http_client(ヘッダーはhttpx2.AsyncClient経由)に変わっていた
なお、ノートブックの作成・編集系ツール(use_notebook など)までは試していません。
「Dockerfile を書いて、ビルドして、動作確認する」という一連の流れを、wslc を知らない LLM が Skill だけで進められたのは、なかなか気持ちよかったです。
前提条件
- WSL 2.9.3 以降(
wsl.exe --versionで確認、wsl --updateで更新) - WSL ディストリビューション内で Copilot CLI を動かしていること
- Windows interop(
/mnt/c)が有効なこと
まとめ
- LLM は新しい機能(今回は WSL Container)をまだ知らないことがある
- でも Docker は知っているので、差分と作法を教えてあげれば十分動く
- 教える手段が Agent Skill。
SKILL.md+ 必要なら補助スクリプトを置くだけ - 決定的にできる処理(パス解決など)はスクリプトに、判断が要る部分は手順書に、と分けると安定する
「LLM が知らない」は、ツールを諦める理由じゃなくて Skill を作るきっかけになる、というのが今回の学びでした。
新しいツールや社内独自のツールでも、同じやり方でいけると思います。みなさんもぜひ自分だけの Skill を作ってみてください!