はじめに

Mac で軽くコンテナを動かしたいだけなのに、毎回 GUI アプリの起動を待たされて、社内 VPN との競合に悩まされる日々が続いていました。

その状態を抜けられそうな選択肢が Apple から登場しました。
apple/container です。

Apple 公式の GitHub リポジトリ
https://github.com/apple/container
プロジェクト自体は2025年6月に 0.1.0 として公開され、安定版の 1.0.0 はその1年後にあたる2026年6月9日にリリースされました。

Apple silicon + macOS 26 (Tahoe) 以降が前提の、Apple 公式コンテナ実行環境です。
本記事では v1.0.0 を私の macOS に入れて、既存のコンテナ利用ワークフローに差し込んでみた一連の体験をまとめます。

なぜ apple/container を試したのか

普段仕事で使っていたのは Rancher Desktop でした。
※Docker Desktop は商用ライセンスの問題があり未利用

ただ、Rancher Desktop も完璧ではありません。
一番引っかかっていたのは社内 VPN との競合で、コンテナ内 DNS 解決が崩れる場面に繰り返し遭遇していました。
だいたいの問題は設定で回避できるのですが、環境を移すたびに思い出して直すのが面倒ですし、その都度作業が止まります。
あと常駐 GUI のメモリ使用量も、愛用の WezTerm + tmux を中心に開発環境を組んでいる私のデスクトップ環境では余分でした。

そんなとき、AI 関連のニュースを漁っていて apple/container 1.0.0 が 2026年6月9日 にリリースされていることを知りました。
Virtualization.frameworkRosetta for Linux を組み合わせた、いわば Docker Desktop の VM 層を Apple 自身がやり直した形です。
手元の macOS は 26.5.1 (Apple silicon、Tahoe) で、ちょうど条件を満たしています。試さない理由はありません。

インストールと最初の動作確認

Apple 公式の署名付き pkg を GitHub Releases から取得して入れます。

gh release download 1.0.0 --repo apple/container --pattern '*.pkg'
sudo installer -pkg container-1.0.0-installer-signed.pkg -target /
container --version
# container CLI version 1.0.0 (build: release, commit: ee848e3)

初回の container system start は対話プロンプトで kernel ダウンロード可否を聞かれるのですが、私の環境では入力経路が拾えず Error: failed to read user input で死にました。--enable-kernel-install を明示するとそのまま起こせます。

container system start --enable-kernel-install
container system status   # running

ここから、Apple silicon ネイティブ / amd64 (Rosetta for Linux 経由) / bind mount 双方向 / ポート公開、の4観点で素の動作を確認しました。

container run --rm docker.io/library/alpine uname -m
# aarch64

container run --rm --arch amd64 docker.io/library/alpine uname -m
# x86_64

container run --rm -v "$(pwd):/work" -w /work docker.io/library/alpine ls

container run --rm -d -p 127.0.0.1:18080:80 nginx:alpine
curl -sI http://127.0.0.1:18080/ | head -1
# HTTP/1.1 200 OK

このあたりは Docker と同じ感覚で叩けて、つまずきませんでした。

Docker コマンドとの対応表

普段 Docker を使っている手で叩くとき、知っておくと楽な置き換えをまとめておきます。

Docker apple/container 備考
docker run --rm -it alpine sh container run --rm -i -t alpine sh ほぼ同じ
docker run --platform=linux/amd64 alpine container run --arch amd64 alpine フラグ名と値が変わる
docker run -v $(pwd):/work alpine container run -v "$(pwd):/work" alpine 同じ
docker run -v $(pwd):/work:ro alpine container run -v "$(pwd):/work:ro" alpine 同じ(:ro は公式コマンド一覧に未記載だが機能する)
docker run -p 127.0.0.1:18080:80 nginx:alpine container run -p 127.0.0.1:18080:80 nginx:alpine 同じ
docker run -m 4g container run -m 4g 同じ(後述、デフォルトが 1 GiB なので明示推奨)
docker build -t myimg . (本稿執筆時点で未確認) 別途確認予定

Docker 慣れした手なら、ほぼ素直に読み替えできるはずです。

実戦投入してみた

ちょうど別件で、ビジュアル回帰テスト(画面のスクリーンショットを基準画像と比較して回帰を検知する仕組み)の整備を進めている最中でした。
手元での基準画像生成が docker run mcr.microsoft.com/playwright:... を起こす運用になっていたので、これを container run に置き換えてみます。
コマンド側の差分は --arch amd64 の指定と、後述する -m 4g の追加だけで済みました。

ところが、生成した基準画像を既存のものと比べるとわずかに byte 数が違いました(170729 bytes170731 bytes、+2 bytes)。
Playwright 側の比較は maxDiffPixelRatio: 0.01 許容で問題なく通りますが、PNG の header chunk の境界揺らぎレベルでズレが見えます。
Docker Desktop も apple/container も、x86-64 エミュレーションには Apple の Rosetta for Linux を使うので、Rosetta 本体の挙動は同じはずです。2 byte 差の原因は Rosetta ではなく、VM / ゲストカーネル / ライブラリといった実行環境のバージョン差で Chromium レンダラの最終出力に微小なゆらぎが乗ったのだろう、と推測しています(Rosetta 本体ではなく実行環境の差かもしれない、という留保付きで)。

基準画像を実行環境に依存させず揃える方向は Docker Desktop に戻すしか取れません。
ならばと、運用基準を pinned image の同一性 + maxDiffPixelRatio: 0.01 + retries 2 で吸収する形に更新しました。
結局このまま既存の CI に乗せて、main 取り込みまで漕ぎ着きました。

ハマりやすいポイント

実戦運用で踏んだ仕様クセを4つ、踏みやすい順に簡潔に。

  1. デフォルトメモリ 1 GiB では Node.js heap が厳しい
    コンテナごとの上限が 1 GiB のため、vite build などで JavaScript heap out of memory が出ます。
    -m 4g を明示するだけで解消します。
  2. ファイル単体の bind mount は弾かれる
    --mount type=bind,source=path '...' is not a directory で reject されます。
    会社の CA 証明書1ファイルを渡したい、というよくある場面で踏みます。
    ディレクトリ単位で bind して、コンテナ内で cp する2段経路が回避策です。
  3. 長時間ジョブの標準出力が完了時に一括 flush される
    コンテナ内で動いているはずの処理ログが、完了するまでホスト側にほぼ出てきません。
    最初はコンテナが死んだのかと疑いましたが、container ls で稼働中のコンテナを確認できれば中で動いています。
    stdout を頼った進捗監視を捨てて完了通知ベースに切り替えるのが楽でした。
  4. container system が止まっていると XPC エラー
    マシン再起動後やセッション切り替え後にいきなり container run を叩くと、XPC connection error: Connection invalid が返ってきます
    container system statusrunning を確認、止まっていたら container system start で起こします。

他に社内プロキシ配下では HTTP_PROXY / HTTPS_PROXY / NO_PROXY-e で明示注入する必要があります(Docker Desktop のように macOS のシステムプロキシ設定を継承しない仕様のようです)。
これも知っていれば container run-e を3つ足すだけで済みます。

まとめ

apple/container 1.0.0 は常駐 GUI なしで RAM を空けられること、Apple silicon ネイティブで起動が数秒で済むこと、この2つが日常の開発体験で有効です。
無設定で全部動くわけではなく、デフォルトメモリやプロキシ環境変数のあたりで一度詰まりますが、一手間かければ回避できます。
Docker のクセに慣れた手で叩けばコマンドの読み替えはほぼ素直に通ります。

私の手元では当面 Rancher Desktop に戻る理由はなさそうです。
Apple silicon の Mac でコンテナの実行環境に悩んでいる方は、一度試してみる価値はあると思います。