💨

Dockerで学ぶポートフォワーディング(port is already allocatedを回避しよう)

に公開

Docker環境のポート番号設定を理解する

Docker環境で開発をしていて、ポート番号設定がうまく出来ずに詰まってしまうことがありました。
本記事ではDocker環境でのポート番号設定とポートフォワーディングについて整理します。

docker-compose.ymlのportsとは何か

Docker環境を構築する際、docker-compose.ymlを利用して、複数コンテナをまとめて管理する場合が多いと思います。
以下はPHPコンテナの設定例です。

services:
    php:
        build:
          context: ./docker/php
        container_name: donetwork_laravel_php
        working_dir: /var/www/test_laravel
        ports:
          - "8080:80"
        networks:
          - laravel_network

この中でコンテナのポート番号を設定するのが、portsです。
portsには、コンテナ外、コンテナ間で通信するためのポート番号設定を記述します。
もし、このportsが無いと、コンテナ外(ホスト)にはコンテナのポートが公開されません。
つまり、コンテナ外(ホスト)から、作成したコンテナへのアクセスはできません。

例を挙げると、webアプリのアプリケーションサーバのコンテナを起動しても、ブラウザからURLを入力してリクエストをした時に、通信が出来ず、画面が描画されないということになります。
これではwebアプリを作る意味がなくなってしまうので、必然的にportsが必要ということになります。

ただし、コンテナ外との通信はできませんが、同じDocker Composeプロジェクトのコンテナ間(同一のdocker-compose.ymlに設定したコンテナ間)では通信することが可能です。
これはDocker Composeで定義したコンテナが通信できる専用のネットワークが、デフォルトで作成されるためです。(詳細は割愛)

portsを詳しく見てみる

portsに焦点を当ててみましょう。

    ports:
      - "8080:80"

コロン” : “を隔ててポート番号が2つ記述されています。
これがポートフォワーディングの設定です。

ポートフォワーディングとは、特定のポート番号宛ての通信を、あらかじめ設定した別のポート番号へ転送することです。

これを設定することで、コンテナのポート番号をコンテナ外へと公開すると同時に、ホストのポート番号宛ての通信を、コンテナへ転送することができるようになります。

左側はホストのポート番号、右側はコンテナのポート番号を記述します。

ホストからコンテナへ向かう通信を、右向きに渡すイメージ(8080→80というように右矢印をイメージ)で考えると覚えやすいと思います。

先ほどの例でいうと、ブラウザがhttp://localhost:8080でリクエストをすると、まずホストのポート番号へアクセスされ、ポートフォワーディングによりポート番号:80のコンテナへ転送されることで、正常にレスポンスを返すことができます。

portsにはどんなポート番号を設定すればよい?

ホスト(左側)とコンテナ側(右側)に分けてそれぞれ整理してみます。

ホスト側のポート番号は自由に決めることができます。

自由にと言われても困ってしまいますが、大体はそのサーバのウェルノウンポートか、それに近い番号を設定していると思います。
例)HTTPサーバの場合、80,81,8080,8081など

ウェルノウンポート以外の値をあえて設定するのは、どのような場合でしょうか?
例えば、バージョンの異なる開発環境を、ポート番号によって切り替えたい場合には有用です。

# Laravel12環境の場合
services:
    php_laravel12:
        # ... その他の設定
        ports:
          - "8080:80"

# Laravel8環境の場合
services:
    php_laravel8:
        # ... その他の設定
        ports:
          - "8081:80"

上記のように設定し、バージョンの異なるLaravel12環境とLaravel8環境を同時に起動する場合を考えてみます。
ブラウザからlocalhost:8080にアクセスするとLaravel12環境にアクセスし、localhost:8081にアクセスするとLaravel8環境にアクセスできます。

開発プロセスにおいては、このように複数の開発環境を切り替えて作業する場合もあるため、ポートフォワーディングの設定を理解しておくことは有用となります。

一方で、コンテナ側のポート番号はほとんどの場合は固定の値になります。
これは、立ち上げたコンテナのアプリケーション自身の設定に依存するためです。
例)Apache2:80、MySQL:3306

ただし、アプリ側の内部ポートの設定を変更することも頑張れば可能です。
Apache2の場合は/etc/httpd/conf/httpd.confで設定の変更ができます。

複数のListenディレクティブを使用して、リスニングするアドレスとポートを複数指定できます。サーバーは、リストされたいずれかのアドレスとポートからのリクエストに応答します。
たとえば、サーバーがポート80とポート8000の両方で接続を受け付けるようにするには、次のように指定します:

Listen 80
Listen 8000

https://httpd.apache.org/docs/2.4/en/mod/mpm_common.html

Docker環境のportsに起因するよくあるエラー「port is already allocated」とその回避法

dockerを使い始めて間もない頃、よく下記のようなエラーに遭遇しました。

Error response from daemon: driver failed programming external connectivity on endpoint laravel.test
: Bind for 0.0.0.0:8080 failed: port is already allocated

これはdocker compose upでコンテナを作成する際に生じるエラーです。
エラーメッセージは「ポート番号が既に割り当てられている」ということを示していますが、何が起きているのでしょうか?
すぐにchatGPTに聞くのもありですが、ここではまずdocker psでコンテナの状態を確認してみます。

$ docker ps
CONTAINER ID   IMAGE                      COMMAND                  CREATED          STATUS          PORTS                    NAMES
c2d1f3a4b5c6   php:8.2-apache             "docker-php-entrypoi…"   2 hours ago      Up 2 hours      0.0.0.0:8080->80/tcp     laravel_test2

docker psでは実行中のコンテナを一覧表示できます。
Dockerドキュメント
(↑嬉しいことにDockerは日本語のドキュメントがあるようです!)
これにより、8080:80のポートフォワーディング設定をしたコンテナが既に存在していることが分かりました。

そのため、エラー解消のためには、新しく作成するコンテナの、ホスト側のポート番号を8080番以外の番号に変更して再実行することが必要です。
また、既存のコンテナを停止することでもエラーが解消されます。

まとめ

Docker仮想環境はネットワークやOSの知識が総動員されており、一気に全てを理解するのは難しいと思います。
私も勉強中ですが、本記事のようにportsとポートフォワーディングだけというように、範囲を限定して理解を深めていくことで開発効率が上がっていくと思います。
本記事が読者の理解の一助になれば幸いです。

参考文献

Discussion