はじめに
つい先日の 2026 年 5 月 26 日に、Canonical から Workshop という新しい開発環境管理ツールが公開されました。
公式には、Workshop は sandboxed development environment を single command で起動するためのツールとして紹介されています。開発環境を一度定義しておけば、別のマシンでも同じ環境を再現しやすくなる、という方向性のツールです。
内部的には LXD を使っており、リポジトリに workshop.yaml や project-local SDK を置くことで、そのプロジェクト専用の開発環境を定義できます。
この記事では、リリース直後の Workshop を試しながら、基本的な使い方を確認しつつ、実際に筆者が開発している自作カーネルプロジェクトで使ってみた構成を紹介します。
Workshop とは
Canonical Workshop は、プロジェクトごとに開発用 sandbox を定義して起動できるツールです。
ざっくり言うと、リポジトリに workshop.yaml や .workshop/ 配下の SDK 定義を置いておくことで、開発に必要な環境を再現できます。
筆者の感覚だと、Docker Compose が「コンテナ群をどう起動するか」に寄っているのに対して、Workshop は「このリポジトリで開発するための作業環境」を定義するみたいな感じに近いです。
特に以下のような用途と相性が良さそうです。
- 開発ツールチェーンを固定したい
- QEMU やクロスコンパイラを使う
- ホスト環境との差分を減らしたい
- リポジトリを clone したあと、少ない手順で build / test / run したい
- プロジェクト固有のセットアップを
.workshop/に閉じ込めたい
基本的な使い方
ここでは、Workshop の基本的なライフサイクルとして、定義、起動、更新、コマンド実行、shell 接続、actions、ログ確認を見ていきます。
この章は 公式 Tutorial: Part 1 をベースにしています。
Workshop は、プロジェクトディレクトリをコンテナ内の開発環境に対応づける仕組みです。プロジェクト側には workshop.yaml を置き、そこに base image、SDK、actions などを定義します。
インストール
Workshop は snap でインストールできます。
sudo snap install --classic workshop
内部では LXD を利用するため、LXD 6.8 以降が必要です。snap で LXD を入れる場合は次のようになります。
sudo snap install --channel=6/stable lxd
既に LXD を使っている場合は、必要に応じて refresh します。
sudo snap refresh --channel=6/stable lxd
workshop.yaml を定義する
Workshop の定義は、プロジェクトディレクトリに置く YAML ファイルです。
最小構成のイメージは次のようになります。
name: dev
base: ubuntu@24.04
sdks:
- name: ollama
name は Workshop の名前です。
base には Ubuntu の base image を指定します。
sdks には、その Workshop に含めたい SDK を指定します。SDK は独自に作成できるほか、公開されている SDK を利用することもできます。
たとえば、Go 環境の SDK を探す場合は次のようにできます。
sdk find go
# NAME VERSION PUBLISHER SUMMARY
# go 1.26.3 Dmitry Lyfar The Go programming language
Workshop を起動する
定義を書いたら、プロジェクトディレクトリで次を実行します。
workshop launch
起動後は、Workshop 内にプロジェクトディレクトリが /project として mount されます。
Workshop の状態は次のコマンドで確認できます。
workshop list
# WORKSHOP STATUS NOTES
# dev Ready -
workshop info
# name: dev
# base: ubuntu@24.04
# project: ~/code/project
# status: ready
# :
workshop info では、base、project path、status、SDK、mount などの情報を確認できます。
start / stop
launch は Workshop を作成して起動する操作です。
一度 launch した Workshop は、必要に応じて stop / start できます。
workshop stop
workshop start
stop は Workshop を削除するわけではなく、起動中の環境を停止する操作です。再び使うときは start で戻せます。
refresh
workshop.yaml や SDK 定義を変更した場合は、Workshop を refresh します。
workshop refresh
base や SDK の定義を変更した場合は、refresh して環境へ反映します。
一方で、後述する actions は workshop run のたびに読み込まれるため、actions の中身だけを変更した場合は refresh なしで試せます。
Workshop 内でコマンドを実行する
Workshop 内でコマンドを直接実行するには workshop exec を使います。
workshop exec dev -- ls /project
単一 Workshop のプロジェクトでは、Workshop 名を省略できる場面もあります。
workshop exec -- ls /project
shell に入る
複数の操作を手動で実行したい場合は、Workshop 内の shell に入れます。
workshop shell
shell に入ると、作業ディレクトリは /project になります。
workshop@dev-xxxx:/project$ pwd
/project
actions を定義する
よく使うコマンドは actions として workshop.yaml に定義できます。
たとえば、Ollama のモデル追加と一覧確認を行うコマンドを、以下のように定義しておくことができます。
actions:
pull: ollama pull "$@"
list: ollama list
こうすると、ホスト側から次のように実行できます。
workshop run -- pull <MODEL>
workshop run -- list
actions は workshop run のたびに読み込まれるため、actions: の内容を少し修正して試すだけなら workshop refresh は不要です。
プロジェクトディレクトリの同期
Workshop では、ホスト側のプロジェクトディレクトリが Workshop 内の /project に mount されます。
そのため、ホスト側で作成したファイルは Workshop 内から見えますし、Workshop 内で作成したファイルもホスト側から見えます。
touch created_outside.txt
workshop exec -- ls /project
workshop exec -- touch /project/created_inside.txt
ls
この挙動のおかげで、ソースコードはホスト側のエディタで編集しつつ、build / test / run だけを Workshop 内で実行する、という使い方ができます。
tasks / changes でログを見る
Workshop では、launch や refresh のような操作が change として記録され、その中の細かい処理が task として確認できます。
workshop changes
# ID STATUS SPAWN READY SUMMARY
# 5 Done today at 14:25 JST today at 14:25 JST Launch "dev" workshop
# 6 Done today at 14:26 JST today at 14:27 JST Execute action "setup"
# 7 Done today at 14:28 JST today at 14:28 JST Execute action "build"
# 8 Done today at 14:29 JST today at 14:29 JST Remove "dev" workshop
特定の change の task を確認するには、change ID を指定します。
workshop tasks <CHANGE_ID>
# STATUS DURATION SUMMARY
# Done 108ms Disconnect interfaces of "system" SDK
# Done 76ms Discard "dev" undesired connections
# Done 3.114s Stop "dev" workshop
# Done 350ms Uninstall "system" SDK
# :
hook が失敗した場合も、workshop tasks <CHANGE_ID> でログを確認できます。
SDK hooks
Workshop では、環境を準備するための hook を定義できます。
特に基本になるのが、setup-base と setup-project です。
setup-base は、Workshop の base 環境を準備するための hook です。system package のインストールや開発ツールの導入など、プロジェクトディレクトリに依存しない初期設定に向いています。
一方で、setup-project は project directory が mount された後に実行される hook です。そのため、/project 配下のファイルを前提にした処理や、プロジェクト固有の初期化に向いています。
たとえば、一般的な CLI / Web アプリ開発環境なら、次のように分けられます。
setup-base:
- apt package のインストール
- Node.js / Python / Go などの開発ツール導入
- OS package として必要なライブラリの導入
setup-project:
- /project に移動
- npm install / pip install / go mod download などの依存取得
- .env.example から .env を作る
- database migration の準備
- project-local な cache directory の作成
setup-base の例です。
#!/usr/bin/env bash
set -euxo pipefail
apt-get update
apt-get install -y \
ca-certificates \
curl \
git \
make \
python3 \
python3-venv \
nodejs \
npm
このように、base 環境そのものに入れておきたいものは setup-base に置くと分かりやすいです。
次に setup-project の例です。
#!/usr/bin/env bash
set -euxo pipefail
cd /project
if [ -f package.json ]; then
npm install
fi
if [ -f requirements.txt ]; then
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
fi
if [ -f .env.example ] && [ ! -f .env ]; then
cp .env.example .env
fi
mkdir -p .cache
setup-project は /project が見える状態で実行されるため、リポジトリ内のファイルを見ながら初期化できます。
この分け方にしておくと、次のように整理できます。
-
setup-base: sandbox の土台を作る -
setup-project: その repository を開発できる状態にする
Workshop はこのように、system-level な準備と project-level な準備を hook として分けて書けるのが便利です。
Workshop で起動されるコンテナの特徴
Workshop は内部的に LXD を利用しており、workshop launch を実行すると、プロジェクトごとの開発用コンテナが作成されます。
実際に起動されたコンテナは、通常のアプリケーションコンテナというより、開発作業用の sandbox に近い性質を持っています。
プロジェクトディレクトリが /project に mount される
Workshop の大きな特徴は、ホスト側のプロジェクトディレクトリがコンテナ内の /project に mount される点です。
そのため、エディタや Git 操作はホスト側で行い、build / test / run は Workshop 内で実行する、という使い方ができます。
workshop shell
で shell に入ると、基本的には /project を作業ディレクトリとして開発できます。
workshop@dev-xxxx:/project$ pwd
/project
この mount は双方向なので、コンテナ内で生成したファイルはホスト側からも見えます。たとえば build artifacts や generated files をそのままホスト側のエディタや Git から確認できます。
一方で、これはコンテナ内の処理がホスト側の作業ツリーを書き換えられるということでもあります。Workshop は開発環境としては非常に便利ですが、未信頼コードを強く隔離するための sandbox として使う場合は、この点を意識しておいた方がよさそうです。
SDK は読み取り専用で mount される
Workshop では、base image の上に SDK を追加して開発環境を構成します。
起動後のコンテナを見ると、SDK は /var/lib/workshop/sdk/<sdk-name> のような場所に mount されます。
/var/lib/workshop/sdk/system
/var/lib/workshop/sdk/rkc-base
SDK は基本的に読み取り専用で mount されるため、Workshop 内で作業していても SDK 本体を直接書き換える形にはなりません。
この構造のおかげで、SDK は「開発環境の部品」として扱いやすくなっています。プロジェクト固有のセットアップは .workshop/<sdk-name>/hooks/ に書き、実際の作業は /project 側で行う、という分離がしやすいです。
apt cache などは Workshop 側で管理される
コンテナ内では apt package をインストールすることもあります。
Workshop では apt cache などの一部ディレクトリが Workshop 側の管理領域から mount されます。これにより、同じ環境を何度も作り直す場合でも、ある程度キャッシュを活用できます。
開発用途では、パッケージインストールの時間を減らせるので便利です。
dev container としての性格が強い
全体として、Workshop のコンテナは「本番アプリケーションを隔離して動かすコンテナ」というより、開発に必要な道具をまとめておく dev container に近い印象です。
- ホストのプロジェクトディレクトリを
/projectに mount する - SDK を読み取り専用で mount する
- actions 経由で build / test / run を実行する
- 必要に応じて tunnel interface でポートを公開できる
- hook の結果や health check を
workshopctl経由で返せる
このあたりの設計を見ると、Workshop は「ホスト環境を完全に隠す」というより、ホストの開発作業とコンテナ内の再現可能なツールチェーンをうまくつなぐための仕組みだと感じました。
実際の使い方を紹介してみる
ここからは、実際に自作 OS プロジェクト Rk-C の開発環境を Workshop で定義した例を紹介します。
GitHubリポジトリはこちら: Rk-C microkernel for RISC-V 64bit
Rk-C の開発では、Nim、clang / lld、QEMU、OpenSBI、RISC-V GNU toolchain、TAP networking などを使います。
これらをホストに直接入れてもよいのですが、特に QEMU / OpenSBI / networking 周りは環境差分が出やすく、README に手順を書く方式だと少し管理しづらさがありました。
そこで、Workshop を使って開発環境をリポジトリ内に定義し、次のような操作で build / test / run できるようにしました。
workshop launch
workshop run -- build
workshop run -- test
workshop run -- run
以降では、その構成を順番に紹介します。
基本構成
今回の構成は次のようにしました。
.
├── workshop.yaml
└── .workshop/
└── rkc-base/
├── sdk.yaml
└── hooks/
├── setup-base
└── setup-project
workshop.yaml には、Workshop 本体の設定と actions を書きます。
.workshop/rkc-base/ は project-local SDK です。
今回は、Rk-C 専用の SDK として、必要なパッケージのインストール、Nim の配置、OpenSBI の clone / build、TAP interface の準備などをまとめました。
workshop.yaml
例として、以下のような定義にしています。
name: rkcdev
base: ubuntu@24.04
sdks:
- name: project-rkc-base
actions:
build: |
set -eux
cd /project
make build
build-bins: |
set -eux
cd /project
make build-bins
test: |
set -eux
cd /project
make test-apps
run: |
set -eux
cd /project
make run
debug: |
set -eux
cd /project
make qemu-debug
clean: |
set -eux
cd /project
make clean
これで、ホスト側から次のように実行できます。
workshop run -- build
workshop run -- test
workshop run -- run
/project にはリポジトリが mount されるので、Workshop 内では /project に移動して make を実行しています。
SDK 定義
.workshop/rkc-base/sdk.yaml は最小限だと次のような形です。
name: rkc-base
version: "0.1.0"
summary: Rk-C development environment
description: |
Development environment for building, testing, and running Rk-C.
license: MIT
プロジェクト固有の SDK として使うので、外部公開用というより、リポジトリに含める開発環境定義という位置づけです。
setup-base
setup-base には、root 権限が必要な初期設定を書きました。
たとえば、apt package のインストールです。
#!/usr/bin/env bash
set -euxo pipefail
apt-get update
apt-get install -y \
ca-certificates \
curl \
git \
make \
python3 \
clang \
lld \
llvm \
qemu-system-misc \
qemu-utils \
device-tree-compiler \
build-essential \
libglib2.0-dev \
libpixman-1-dev \
gcc-riscv64-linux-gnu \
binutils-riscv64-linux-gnu
Rk-C では OpenSBI を build するために riscv64-linux-gnu- toolchain が必要なので、gcc-riscv64-linux-gnu と binutils-riscv64-linux-gnu もここで入れています。
そのほかにも必要となる Nim のインストールや OpenSBI のビルドなども setup-base で行っています。
setup-project
setup-project には、project mount 後に実行したい処理を書きました。
今回の場合は、OpenSBI の build や TAP interface のセットアップなど、プロジェクトに依存する処理をこちらに置いています。
TAP networking も Workshop 内で準備できるようにしたことで、ホスト側で次のような作業をしなくて済むようになりました。
sudo ip tuntap add dev tap0 mode tap user "$USER"
sudo ip link set tap0 up
sudo ip addr add 10.0.1.1/24 dev tap0
sudo sysctl -w net.ipv4.ip_forward=1
QEMU / TAP 周りは環境差分が出やすいので、ここを Workshop 側に閉じ込められるのはかなり便利でした。
GDB port を公開する
qemu-debug では QEMU が GDB server を localhost:1234 で待ちます。
ただし、Workshop 内の localhost:1234 はそのままだとホストから見えません。
この場合は tunnel interface を使って、Workshop 内の port を host に公開できます。
sdks:
- name: project-rkc-base
slots:
gdb:
interface: tunnel
endpoint: localhost:1234
- name: system
plugs:
gdb:
interface: tunnel
endpoint: localhost:1234
イメージとしては、
- コンテナ内[SLOT]に対して
- ホスト側[PLUGIN]を接続する
という感じです。
これで workshop run -- debug 後に、ホスト側から次のように接続できるようになります。
gdb-multiarch bin/kernel.elf
(gdb) target remote localhost:1234
実際に使う
最終的に、Rk-C の開発では次のような流れになります。
workshop launch
workshop run -- build
workshop run -- test
workshop run -- run
デバッグする場合は次のようにします。
workshop run -- debug
Docker Compose との違い
今回触ってみて、Workshop は Docker Compose よりも「リポジトリに紐づいた開発環境」として使いやすい印象でした。
Docker Compose は、複数サービスを起動する用途では非常に便利です。
一方で、今回のように、
- QEMU を動かす
- クロスコンパイラを入れる
- OpenSBI を build する
- TAP interface を準備する
- project directory を前提に build / test / run action を定義する
といった用途では、Workshop の方が自然に感じました。
特に project-local SDK と actions の組み合わせがよく、環境構築 / 動作確認の手順をかなり短くできます。
よかったところ
実際に使ってみてよかった点は以下です。
- リポジトリ内に開発環境定義を置ける
-
workshop launchで環境を起動できる -
workshop run -- build/test/runのように操作を actions として定義できる - QEMU や OpenSBI などの環境差分を吸収しやすい
- project-local SDK でプロジェクト固有のセットアップを管理できる
- tunnel interface で debug port を host に公開できる
- task log が見やすく、hook 失敗時の調査がしやすい
まとめ
Canonical Workshop を使って、Rk-C の開発環境をリポジトリ内に定義してみました。
結果として、従来 README に書いていた host setup 手順の多くを Workshop SDK に移せました。
特に QEMU / OpenSBI / RISC-V toolchain / TAP networking のような、環境差分が出やすい部分を sandbox 側に閉じ込められたのが大きかったです。
自作 OS や低レイヤー系プロジェクトのように、開発環境の再現が難しくなりがちなプロジェクトでは、Workshop はかなり相性が良いと感じました。
ぜひ皆さんも workshop を使ってみてください。