🌐

Apple container はコンテナ毎にIPを持つ

に公開

この記事でやること

Apple 純正のコンテナCLI container の特色(コンテナごとに独立したIPを持つ)を使って、複数のアプリを全部 :80(や :3000)のまま同時に動かす手順をまとめます。docker run -p 3001:3000-p 3002:3000…とホストのポートを取り合うあの作業から解放されます


何が問題だったか(Docker Desktop の場合)

Docker Desktop(や Colima)は、コンテナのネットワークがホストの単一 localhost 越しに見えます。なので複数のアプリを動かすと:

docker run -d -p 3000:3000 frontend     # localhost:3000
docker run -d -p 3001:3000 admin        # localhost:3001 ← ずらす
docker run -d -p 3002:3000 storybook    # localhost:3002 ← さらにずらす

ホスト側ポートを手作業でずらすことになり、.env やドキュメントに「ローカルは 3001」みたいなローカル専用の差分が増えていきます。


Apple container は「コンテナ毎にIP」

Apple container は各コンテナを軽量VMとして起動し、vmnet で1つずつ独自IPを割り当てます。Docker Desktop との違いを図にするとこうです。

Docker Desktop は全部 localhost なので 3000 が埋まると 30013002 …とホスト側ポートをずらして見分ける必要があります。Apple container は各コンテナが別IPなので、全員 :80 のまま -p なしで衝突しません。
実際に確認してみます。

# 3つとも同じ :80 のまま起動(-p は付けない)
container run -d --name web   nginx:alpine
container run -d --name api   nginx:alpine
container run -d --name admin nginx:alpine
container ls
NAME    IMAGE               STATE    IP
web     nginx:alpine        running  192.168.64.29/24
api     nginx:alpine        running  192.168.64.30/24
admin   nginx:alpine        running  192.168.64.31/24

それぞれ別IPが振られています。IP は container ls の IP 列で見えるほか、スクリプトでは container ls --format jsonjq で抜けます。

container ls --format json | jq -r '.[] | select(.configuration.id=="web") | .status.networks[0].ipv4Address'
# → 192.168.64.29/24

そのままホストから各IPの :80 に到達できます-p 不要)。

curl -s -o /dev/null -w "%{http_code}\n" http://192.168.64.29/   # web   → 200
curl -s -o /dev/null -w "%{http_code}\n" http://192.168.64.30/   # api   → 200
curl -s -o /dev/null -w "%{http_code}\n" http://192.168.64.31/   # admin → 200

3 つとも :80 のまま、衝突なし。「ポート番号をずらす」という発想自体が要らなくなります


名前でアクセスしたい(web.test のように)

IP 直打ちは味気ないので名前を付けたいところです。

ポイントは、自分で「名前→IP の対応」を用意しないといけないこと(コンテナへの到達自体は IP 直で標準で動くので、足すのは名前だけ)。一番手軽なのは /etc/hosts に1行書く方法です。名前→IP さえ与えれば普通に届くのは curl --resolve でも確認できます。

IP=$(container ls --format json | jq -r '.[] | select(.configuration.id=="web") | .status.networks[0].ipv4Address' | cut -d/ -f1)
curl --resolve "web.test:80:$IP" http://web.test/   # → 200(名前→IPを与えれば届く)

一番手軽なのは /etc/hosts に手動で1行(192.168.64.29 web.test)足すことですが、IP はコンテナを作り直すと変わるので、起動中のコンテナから自動で /etc/hosts を更新するのが実用的です。

/etc/hosts を「マーカーブロック」で sync / reset する

この「名前レイヤ」は Apple container 標準には無いので、自前で足します。コツは /etc/hosts の中に自分用のブロックをマーカーで囲むこと。そうすれば「稼働中コンテナから一括反映(sync)」と「自分の追記だけ全消し(reset)」をワンコマンドにできます。~/.zshrc に次の2関数を入れておくと便利です。

# 稼働中コンテナを <名前>.test として /etc/hosts に反映
ctr-hosts-sync() {
  local b='# >>> apple-container >>>' e='# <<< apple-container <<<'
  local entries
  entries=$(container ls --format json | jq -r \
    '.[] | select((.configuration.id|test("buildkit"))|not)
         | "\(.status.networks[0].ipv4Address|split("/")[0]) \(.configuration.id).test"')
  sudo sed -i '' "/$b/,/$e/d" /etc/hosts                       # 既存ブロックを削除
  printf '%s\n%s\n%s\n' "$b" "$entries" "$e" | sudo tee -a /etc/hosts >/dev/null
  echo "synced:"; echo "$entries"
}
# 自分のブロックだけ消す(他の行は触らない)
ctr-hosts-reset() {
  sudo sed -i '' '/# >>> apple-container >>>/,/# <<< apple-container <<</d' /etc/hosts
  echo "reset done"
}
ctr-hosts-sync     # コンテナ起動後に1発 → web.test / api.test … が引けるように
ctr-hosts-reset    # シュッと全消し(apple-container ブロックだけ)
  • マーカー行で囲むので、resetlocalhost 等の既存行に触れず自分の追記分だけ消えます。
  • sync は「消してから足す」ので何度叩いても重複しません(IP が変わったら sync し直すだけ)。
  • buildkit などの裏方コンテナは除外しています。

使いどころ

コンテナ毎IP(+必要なら名前)が効くユースケース例:

  • PR / ブランチごとのプレビューを同時起動pr-123.test pr-124.test を全部 :3000 のまま並べる。ポート割当の管理が不要。
  • OAuth / Cookie / CORS の検証app.test api.test admin.test を別ホスト名・別IPで立て、localhost では再現しづらい SameSite Cookie・redirect URI・CORS の挙動をローカルで確認。
  • 複数スタックを素の設定のまま:Rails も Next も Storybook も全部 :3000 で起動。.env をローカル用にいじらなくて済む(※ dev サーバが 0.0.0.0 で listen している前提。127.0.0.1 固定だとコンテナIP経由では届かないので、bind アドレスだけ 0.0.0.0 にする)。
  • 送信元IP依存の機能:rate limit・IP allowlist などを、別IPのコンテナから叩いて確認(Docker Desktop の NAT 越しだと送信元が潰れがち)。

まとめ

  • Apple container はコンテナごとに独自IPを持つので、-p なし・ポート衝突なしで複数アプリを同じポートのまま同時起動できる。これが Docker Desktop との明確な差。
  • 名前での自動解決は標準には無いcontainer system dns create はコンテナ名を登録しないので、名前運用は /etc/hosts か簡単な自動化スクリプトで足す。
  • 「localhost にポートを全部ぶら下げて番号で見分ける」世界から、「コンテナごとにIP(と名前)を持つ小さなネットワーク」へ、という発想の転換が一番のうまみです。

Discussion