リモート開発メインのソフトウェア開発企業のエンジニアブログです

[Docker Compose] そのポート公開、本当に必要?

Docker がローカル開発環境の基盤のメインストリームになってから大分経ち、大抵のプロジェクトでは Docker Compose が使われていると思います。

私は普段から複数のプロジェクトに横断的に参画していて色々な Compose Stack を見ますが、不必要にホストマシンにポートを公開しているケースにたまに遭遇します。おそらく、フレームワークのテンプレートや Docker Hub で公開されている設定例を使い回してそのままにしてるパターンが多いのではないでしょうか。

そのプロジェクトだけに関わっている場合は特に問題ないんですが、複数プロジェクトに関わっていて複数の Compose Stack を使っていると、ポートの衝突が発生してサービスが起動しない問題が出てきます。そこで、今回は設定の改善案を提案しますので、これを機に今一度見直しをしてみるのはいかがでしょうか。

なお、今回の話は全てデフォルトのネットワーク設定である bridge ドライバを使っている場合に限ります。

1. そもそも公開する必要があるかどうかを見極める

以下は適当なプロジェクトの compose.yaml の例 (細かいところは省略) です。この様につい愚直に全サービスに ports: をベタ書きしがちですが、まずは「本当にホスト側に公開すべきか」をしっかり見極めましょう:

services:
  rails:
    build: 
      context: .
    command: bundle exec rails server -b 0.0.0.0
    ports:
      - "3000:3000"
      - "5432:5432" # <- Vite でフロントエンドの開発サーバーも起動する想定
    # ...  

  mysql:
    image: mysql
    ports:
      - "3306:3306"
    # ...

  localstack:
    image: localstack/localstack
    ports:
      - "4566:4566"
    # ...

  mailpit:
    image: axllent/mailpit
    ports:
      - "1025:1025" # SMTP
      - "8025:8025" # Web UI
    # ...

順番に掘り下げてみます。

  • rails
    • Rails server の 3000 と Vite Dev Server の 5432 は実際にブラウザからのアクセスで必要
  • mysql
    • アプリ (Rails) からは Docker ネットワークから mysql:3306 の様に接続するのでホストへの公開は基本的には不要
    • ただしホストから GUI などで繋ぎたい場合は必要。コンテナ内の CLI (例: docker compose exec -T mysql mysql) で十分なら不要。
  • localstack (AWS リソースのモック. MinIO, DynamoDB local なども同様)
    • これも MySQL と同様の理由で不要。ホストから AWS CLI で直接 API 実行したい場合でも後述の方法で可能。
  • mailpit (SMTP のモック. MailCatcher や MailHog も同様)
    • SMTP の 1025 も、MySQL や LocalStack と同様の理由で不要
    • Web UI の 8025 はブラウザからアクセスしたいので必要

要するにホストマシンから直接 127.0.0.1 で繋ぎたいサーバーなどで原則公開する様にしましょう。以下は設定例です:

services:
  rails:
    build: 
      context: .
    command: bundle exec rails server -b 0.0.0.0
    ports:
      - "3000:3000"
      - "5432:5432" # <- Vite でフロントエンドの開発サーバーも起動する想定
    # ...  

  mysql:
    image: mysql
    # ...

  localstack:
    image: localstack/localstack
    # ...

  mailpit:
    image: axllent/mailpit
    ports:
      - "8025:8025" # Web UI
    # ...

公開ポートが半分に減りました。また LocalStack は、 AWS CLI から API の実行はしたい時は次の方法で可能です:

docker run -it --rm --network [ここにネットワーク名] \
  -e AWS_ACCESS_KEY_ID=dummy \
  -e AWS_SECRET_ACCESS_KEY=dummy \
  amazon/aws-cli --endpoint-url http://localstack:4566/ s3 ls

--network オプションで Compose で使われているネットワーク名を指定すると、その上で AWS CLI のコンテナを起動できるので localstack:4566 で接続可能になります。ネットワーク名はプロジェクト毎に異なるので docker network ls で探してみてください。

Moba Pro

2. 必要な場合でも .env で可変にしておく

ポート公開が避けられない場合も、固定数字をハードコードしない だけで衝突リスクは激減します。Laravel Sail なんかはこの手法を使っていますね。

Docker Compose はデフォルトで .env の中に定義されている環境変数が設定ファイルの中で有効になります。以下は .envcompose.yaml の設定例です:

# .env

APP_PORT=12000               # Rails
VITE_PORT=12001              # Vite Dev Server
FOWARD_DB_PORT=12002         # MySQL
FOWARD_MAILPIT_UI_PORT=12003 # Mailpit Web UI
# compose.yaml

services:
  rails:
    build: 
      context: .
    command: bundle exec rails server -b 0.0.0.0
    ports:
      - "${APP_PORT:-3000}:3000"
      - "${VITE_PORT:-5432}:5432"
    # ...  

  mysql:
    image: mysql
    ports:
      - "${FORWARD_MYSQL_PORT:-3306}:3000"

  localstack:
    image: localstack/localstack
    # ...

  mailpit:
    image: axllent/mailpit
    ports:
      - "${FOWARD_MAILPIT_UI_PORT:-8025}:8025"

ポイントは、${APP_PORT:-3000} の様にデフォルト値 (この場合 3000) を定義する事で .env に定義がない場合でも問題なく動作する点です。

これで自由に公開するポートを設定できる様になりました。ただ他のメンバーと URL を共有する場合はポートが異なる可能性を考慮する必要があります。

番外編

実は次の様にしてホスト側のポートを省略すると、 Docker が適当に使えるポートを使って公開してくれます:

  mailpit:
    image: axllent/mailpit
    ports:
      - "8025"

ただ多くの場合は固定した方が利便性が高いと思うので .env を使うのが良いと思います。

まとめ

  1. まずは公開不要かどうかを真面目に考える
    内部通信だけなら同一ネットワークで十分。
  2. 必要なら .env で柔軟に
    ポート番号をコードから追い出せば、競合は最小化できる。

← 前の投稿

次の投稿 →

[Laravel] PHPUnitテスト初心者ガイド

コメントを残す