プログラミング
DockerサンドボックスによるESP32ファームウェア開発
ESP32 Firmware Development with Docker Sandboxes (docker.com)
要約
この記事では、DockerとDockerサンドボックスを活用して、特にESP32プロジェクトにおけるファームウェア開発の再現性と効率を向上させる方法を探ります。公式Dockerイメージによるビルドの基本から、複数のIDFバージョンを並行して扱う方法、そしてAIコーディングエージェントを安全に利用するためのDockerサンドボックスの活用まで、実践的なワークフローを紹介します。
全文翻訳
Docker Captain 再現可能なESP32ファームウェア開発をDockerとDockerサンドボックスで 2026年8月14日投稿 Marco Franzon ファームウェア開発は常に困難を伴います。ツールチェーンの不一致、「私のマシンでは動く」というビルド、レガシー製品の保守と新機能のリリースとの間の緊張関係などです。この記事では、特にESP32プロジェクトにおいて、DockerとDockerサンドボックスをどのように活用してファームウェア開発を容易にできるかを探ります。現在、チームはWi-Fi 6、Matter、電力最適化などの新機能のイテレーションを行いながら、複数のハードウェアリビジョン、いくつかのESP-IDFリリース、および長期的な顧客展開をサポートすることになります。公式のespressif/idf Dockerイメージは再現性の問題を解決します。Docker Sandboxes (sbx CLI) は、より新しい問題、つまりAIコーディングエージェントにラップトップのキーを与えずに、ファームウェアをフルスピードで作業させることを解決します。この記事では、これら両方を組み合わせた実践的なワークフローを説明します。クリーンなビルド、新しいファームウェアとレガシーファームウェアのための並列環境、そして安全な無人AIセッションです。
パート1: ベースライン – 公式イメージでのビルド
espressif/idf イメージは、完全でピン留めされたESP-IDFインストールを提供します。フレームワーク自体、Xtensa/RISC-Vツールチェーン、Python環境、CMake、ninja、すべてが含まれています。ビルドには1つのコマンドが必要です:
docker run --rm -v $PWD:/project -w /project \
-u $UID -e HOME=/tmp \
espressif/idf:release-v5.4 idf.py build
カーゴカルト(無批判な模倣)ではなく理解すべきいくつかの詳細:
-u $UID -e HOME=/tmp は、コンテナをユーザーとして実行するため、build/内のビルド成果物がrootの所有にならないようにします。HOME=/tmp は、IDFツールにキャッシュ用の書き込み可能なホームを提供します。
タグをピン留めしてください。latest はマスターブランチを追跡し、最終的にはあなたを壊します。vX.Y タグは固定リリースです。release-vX.Y タグはリリースブランチを追跡し、バグ修正を受け取ります。メンテナンス中の製品では、正確なvX.Y.Zタグが最も安全です。アクティブな開発では、release-vX.Y が良いバランスです。
マウントされたプロジェクトがコンテナ内のユーザーとは異なるユーザーによって所有されている場合、Gitは「疑わしい所有権」について不平を言います。イメージはパスをホワイトリスト化するために -e IDF_GIT_SAFE_DIR='/project' をサポートしています(複数のパスを区切るには:を使用)。
コンパイラキャッシュを -e IDF_CCACHE_ENABLE=1 で有効にし、ボリュームをマウントして実行間で永続化します。中規模プロジェクトの完全なリビルドは、数分から数秒に短縮されます。
フラッシュとモニタリング
Linuxでは、シリアルデバイスをパススルーします:
docker run --rm -it \
--device=/dev/ttyUSB0 \
--group-add $(getent group dialout | cut -d: -f3) \
-v $PWD:/project -w /project \
-u $UID -e HOME=/tmp \
espressif/idf:release-v5.4 idf.py flash monitor
--group-add は、$UIDとして実行しておりrootではないため、デバイスノードがdialoutに属しているために必要です。macOSとWindowsでは、Docker DesktopはUSBデバイスをコンテナにパススルーできません。クリーンな回避策は、RFC2217を使用したネットワークシリアルブリッジであり、esptoolはネイティブでサポートしています。
ホストで:
pip install esptool esp_rfc2217_server
-p 4000 /dev/cu.usbserial-1420
コンテナ内で、id f.py をネットワークポートに向けます:
idf.py --port 'rfc2217://host.docker.internal:4000?ign_set_control' flash monitor
これはハックのように見えますが、実際には機能です。シリアルポートがネットワークエンドポイントになると、何でもそれに到達できます。コンテナ、CIランナー、そして(後述する)サンドボックス化されたAIエージェントです。このトリックを覚えておいてください。パート3の要です。
Makefile の背後に隠す
誰もこれらのコマンドを2回入力すべきではありません。小さなMakefileは、配管が変更されてもインターフェースを安定させます:
IDF_IMAGE ?= espressif/idf:release-v5.4
PORT ?= /dev/ttyUSB0
DOCKER_RUN = docker run --rm -it \
--device=$(PORT) \
--group-add $(shell getent group dialout | cut -d: -f3) \
-v $(PWD):/project -w /project \
-v idf-ccache:/ccache -e CCACHE_DIR=/ccache -e IDF_CCACHE_ENABLE=1 \
-u $(shell id -u) -e HOME=/tmp -e IDF_GIT_SAFE_DIR=/project \
$(IDF_IMAGE)
build:
$(DOCKER_RUN) idf.py build
flash:
$(DOCKER_RUN) idf.py flash
monitor:
$(DOCKER_RUN) idf.py monitor
menuconfig:
$(DOCKER_RUN) idf.py menuconfig
shell:
$(DOCKER_RUN) bash
これで、make build はすべての開発者とCIで同様に機能し、IDFバージョンを切り替えるのは make build IDF_IMAGE=espressif/idf:release-v5.3 のようになります。
パート2: 並列環境 – 新機能とレガシー、並べて
ここでコンテナアプローチは単に便利であるだけでなく、働き方を変え始めます。各コンテナは完全に分離されているため、同じマシン上で2つの異なるIDFバージョンを2つの異なるボードに対して同時に実行できます。
# ターミナル1 - 新機能ブランチ、IDF 5.4、実験用ボード
docker run --rm -it --device=/dev/esp32-experimental \
-v $PWD/new-feature:/project -w /project \
-u $UID -e HOME=/tmp \
espressif/idf:release-v5.4
# ターミナル2 - レガシーファームウェア、IDF 5.3、本番用ボード
docker run --rm -it --device=/dev/esp32-production \
-v $PWD/legacy:/project -w /project \
-u $UID -e HOME=/tmp \
espressif/idf:release-v5.3
典型的な用途:
1つのボードで実験的なコードをフラッシュしながら、長時間のストレステストや顧客デモはもう一方でそのままにしておく。ファームウェアバージョン間で電力消費をA/B比較する。現在のツールチェーンで修正を開発しながら、正確なレガシーツールチェーンでフィールドバグを再現する。
udev による安定したデバイス名
/dev/ttyUSB0 と /dev/ttyUSB1 はプラグの順序によって入れ替わり、最終的には間違ったボードをフラッシュさせることになります。Linuxでは、アダプターのシリアル番号をキーにしたudevルールでそれらをピン留めします:
# シリアル番号を見つける
udevadm info -a /dev/ttyUSB0 | grep '{serial}'
# /etc/udev/rules.d/99-esp32.rules
SUBSYSTEM=="tty", ATTRS{serial}=="A50285BI", SYMLINK+= "esp32-experimental"
SUBSYSTEM=="tty", ATTRS{serial}=="B7743NM0", SYMLINK+= "esp32-production"
udevadm control --reload の後、シンボリックリンクは再起動や再接続後も維持され、Makefileターゲットは列挙の事故ではなく、役割でボードを参照できます。
またはComposeでコード化する
2つの環境設定が永続的な場合、compose.yaml はシェル履歴よりもそれを良く文書化します:
services:
new-feature:
image: espressif/idf:release-v5.4
volumes:
- "./new-feature:/project"
working_dir: /project
devices:
- "/dev/esp32-experimental:/dev/ttyUSB0"
stdin_open: true
tty: true
legacy:
image: espressif/idf:release-v5.3
volumes:
- "./legacy:/project"
working_dir: /project
devices:
- "/dev/esp32-production:/dev/ttyUSB0"
stdin_open: true
tty: true
docker compose run new-feature idf.py flash monitor
そして、役割から物理ボードへのマッピングはバージョン管理されます。
パート3: Docker Sandboxes – AIエージェントに無人で作業させる
Claude Codeのようなコーディングエージェントは、ファームウェア作業に非常に役立ちます。IDFバージョンの間でコンポーネントを移植したり、単体テストを書いたり、sdkconfig の設定ドリフトを追跡したりします。しかし、役立つためには、ビルド、フラッシュ、pip install、時にはDocker自体を実行する必要があります。エージェントにラップトップ上で直接、権限バイパスモードでその自由を与えることは、正当な理由から不快です。Docker Sandboxesは、コンテナよりも強力なプリミティブでこれを解決します。各サンドボックスは、独自のカーネル、ファイルシステム、ネットワークスタック、および独自のプライベートDockerデーモンを持つmicroVMです。エージェントはパッケージをインストールしたり、システム設定を変更したり、コンテナをビルドして実行したりでき、それらのどれもホストに触れません。ワークスペースディレクトリは同じパスでサンドボックスに同期されるため、エラーメッセージ内のファイルパスは両方の世界で一致します。
CLIは小さく明確です:
# 現在のプロジェクトのためにサンドボックスでClaude Codeを開始する
sbx run claude
# 特定のディレクトリで作業する
sbx run claude ~/firmware/new-feature
# 実行中のもの、リソース使用量、ネットワークリクエストを確認する
sbx
# リストとクリーンアップ
sbx ls
sbx rm new-feature
特にファームウェア作業において重要な3つの特性があります:
破棄可能性。エージェントは、esptoolのバージョン、パーティションテーブル、またはカスタムツールチェーンを実験して、その環境を台無しにすることができます。sbx rm で、それはなかったことになります。あなたのホストIDF設定(もしあれば)は変更されません。
ネットワークポリシー。サンドボックスは、3つのモードを持つホスト側のプロキシを介してトラフィックをルーティングします: オープン、バランス(事前承認された開発者とパッケージマネージャーに対するデフォルト拒否