Docker Composeのネットワーク設定と通信を理解する
Composeのデフォルトbridge、サービス名によるDNS、hostモードの使いどころ、複数プロジェクトでのネットワーク共有、UFWを迂回する公開ポートまで解説します。
アプリケーションの起動前に Compose が構築するもの
Docker Compose のネットワークは、1 つのルールから始まります。docker compose up はプロジェクト用のプライベートネットワークを作成し、すべてのサービスを接続し、サービス名で相互に通信できるようにします。そのために networks: の行を 1 行書く必要はありません。Compose のネットワークで混乱しやすいのは、このデフォルト設定が最初から存在することを把握していないためです。
小さなファイルを示します。ディレクトリ shop を作成し、その中に compose.yaml として保存してください。
services:
web:
image: nginx:1.27
ports:
- "8080:80"
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example起動して、Docker が作成したものを確認します。
docker compose up -d
docker network ls一覧に shop_default というネットワークが追加されています。Compose はこれを <project>_default と命名し、プロジェクト名にはデフォルトでディレクトリ名の小文字表記を使用します。docker compose -p myproject up -d またはファイル内のトップレベルの name: myproject で上書きできます。ドライバーは bridge です。これはホスト内部の仮想スイッチです。各コンテナにはプライベートサブネット上のアドレスが割り当てられ、外向きのトラフィックは送信時にホストのアドレスへ変換されます。
docker compose down を実行すると、そのネットワークも削除されます。古いプロジェクトのコンテナがネットワークを使用中のまま残っていると、ネットワークを保持し続けるのはこのためです。Docker は error while removing network: network shop_default has active endpoints を返して削除を拒否します。対処するには、そのネットワークに接続されたコンテナを停止するか削除してください。
Compose を初めて使用する場合は、Compose ファイルの構成とライフサイクルコマンドを先に読むとよいでしょう。以降の説明は、プロジェクトを起動および停止できることを前提としています。
サービス名による DNS 解決は初心者が見落としやすい点です
ユーザー定義ネットワークでは、Docker が組み込み DNS サーバーを実行しており、各コンテナから 127.0.0.11 で利用できます。この DNS は、サービス名を現在のコンテナアドレスに解決します。そのため、web は設定なしで、ホスト名 db のデータベースへポート 5432 で接続できます。
docker compose exec web getent hosts db次のような行が出力されます: 172.18.0.2 db。何も出力されない場合、2 つのサービスが同じネットワーク上にありません。
ほとんどの人が一度は犯す間違いは、アプリケーション設定で localhost を使うことです。コンテナ内では、localhost はそのコンテナ自身を指します。ホストや別のサービスは指しません。Postgres クライアントでは、これが明確に示されます。
could not connect to server: Connection refused
Is the server running on host "localhost" (127.0.0.1) and accepting
TCP connections on port 5432?接続文字列は postgresql://postgres:example@db:5432/postgres にします。ホスト部分にはサービス名を指定します。
後で時間を節約できる重要な点が 2 つあります。名前は現在実行中のコンテナに解決されるため、docker compose up -d --scale web=3 は 1 つの名前に 3 つのアドレスを返します。DNS を無期限にキャッシュするクライアントは、停止したコンテナに固定されます。また、単純な docker run で --network を指定しない場合に使われる従来の bridge ネットワークでは、コンテナ名を解決できません。このネットワーク上のコンテナは IP アドレスでのみ互いを見つけ、外部ホスト名はホストの /etc/resolv.conf のコピーを使って解決します。そのため、2016 年のコンテナリンクに関する説明は、現在の動作と一致しません。
2 つのサービスを接続するために ports: は必要ありません
ports: は、コンテナのポートをホスト上に公開します。これは、Docker の外部から到着するネットワークトラフィックに使用します。サービス間の通信とは関係ありません。同じプロジェクトネットワーク上では、ポート範囲全体を使用してサービス間通信がすでに機能します。
そのため、多くの人がデータベースサービスに追加する ports: - "5432:5432" は役に立たないだけでなく、実害があります。サーバーのパブリックインターフェースで Postgres を公開してしまうためです。削除してください。移行のためにノート PC から接続する場合は、"127.0.0.1:5432:5432" で loopback にバインドし、SSH トンネル経由で接続してください。listening socket、公開ポート、ファイアウォールルールの違いについては、Linux でポートと待ち受けサービスが動作する仕組みで説明しています。
Compose における expose: は、ドキュメントとしてのみ機能します。同じネットワーク上のコンテナ間では通信が閉じられていないため、何も開きません。
network_mode host が適する場合と、その代償
host モードでは、コンテナ独自のネットワーク名前空間を使わず、プロセスがホストのインターフェースを直接使用します。
services:
probe:
image: alpine:3.20
network_mode: host
command: sleep infinityこれを使う明確な理由があります。メディアサーバーやホームオートメーションハブのデバイス検出など、ローカルネットワーク上のブロードキャストまたはマルチキャストトラフィックを確認する必要があるプロセスは、bridge の背後からそのトラフィックを確認できません。bridge はそのトラフィックをコンテナへ転送しないためです。ホストのインターフェースカウンターを読み取る監視エージェントにも、ホストのインターフェースが必要です。また、アドレス変換の経路を省略できます。これはパケットレートが高い場合に重要です。
代償は明確です。
ports: は機能しません。Docker は、host ネットワークモードでは公開ポートの設定が破棄され、コンテナはプロセスが bind するポートに bind すると警告します。ポート 8080 を使おうとする host モードのコンテナが 2 つあると競合し、2 つ目は bind: address already in use で終了します。
サービス名による名前解決は、両方向で利用できません。コンテナはプロジェクトネットワーク上にないため、db を解決できず、他のサービスもそのコンテナを解決できません。ただし、通常は 127.0.0.1 で、ホスト上に公開されたポートを介して他のサービスへ接続できます。Linux では、bridge ネットワーク上のコンテナの IP アドレスに対して、待ち受け中の任意のポートへ直接接続することもできます。ホストは bridge ネットワーク上のコンテナへ自由にアクセスでき、host モードのコンテナはホストのネットワークスタックを共有するためです。コンテナを再作成するとアドレスは変わるため、loopback に公開したポートを使う方法が安定した経路です。
分離もなくなります。host モードのコンテナ内でプロセスが 0.0.0.0 に bind すると、apt でインストールしたパッケージと同じように、サーバーのすべてのインターフェースで待ち受けます。パブリックインターフェースも含まれます。利点は 1 つあります。このトラフィックは通常の入力経路を通るため、UFW のルールが適用されます。公開ポートではそうなりません。
host モードは Linux の Docker Engine の機能です。Docker Desktop でのサポートは version 4.34 以降に限られ、事前に有効化する必要があります。さらに、コンテナはホストの IP アドレスに bind できず、処理対象は TCP と UDP に限られます。チームの半分が Linux サーバーを使い、残りが Docker Desktop を使っている場合、同じファイルでも動作が異なると考えてください。
ホストのインターフェースが必要な場合に host モードを使います。接続問題を解決するために使ってはいけません。通常は、1 つの問題をさらに難しい問題へ置き換えるだけだからです。
Compose プロジェクト間を external network で接続する
一方のプロジェクトが作成したネットワークは、別のプロジェクトからは見えません。そのため、同じサーバー上でも proxy/compose.yaml のリバースプロキシから app/compose.yaml のアプリを参照できません。解決策は、どちらのプロジェクトも所有しないネットワークを用意することです。
手動で 1 回だけ作成します。
docker network create edge次に、各プロジェクトで external として宣言します。プロキシ側は次のようにします。
services:
proxy:
image: traefik:v3.1
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- edge
networks:
edge:
name: edge
external: trueアプリケーション側は次のようにします。
services:
app:
image: nginx:1.27
networks:
- edge
- internal
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example
networks:
- internal
networks:
edge:
name: edge
external: true
internal:external: true は、Compose に既存のネットワークへ接続させ、新しいネットワークを作成させない指定です。また、docker compose down 上にそのネットワークを残します。独立した name: キーも重要です。これがない場合、Compose は edge という名前と完全に一致するネットワークを探します。このキーを使うと、ファイル内の名前とホスト上の名前を別々に指定できます。
ネットワークが存在しない場合、Compose は起動を拒否し、そのネットワークが external として宣言されているものの見つからないと報告します。先にネットワークを作成してください。
アプリケーションのファイルで internal がどのように指定されているか確認してください。データベースはそのプロジェクト専用のネットワークにのみ接続されるため、プロキシからは到達できず、app だけが接続できます。ネットワークの下に internal: true を追加すると、外部ネットワークへのルートも完全に削除されます。これはデータベースの適切なデフォルト設定です。ただし、設定前に把握しておくべき注意点があります。internal ネットワーク上のコンテナは何もダウンロードできません。そのため、起動時に apt-get update または pip install を実行する entrypoint はハングし、最終的にタイムアウトで失敗します。
ルーティングルールと証明書を含む完全な構成例については、1 つの Traefik インスタンスの背後で複数のアプリを実行するを参照してください。
公開ポートは UFW を迂回します
Compose のネットワーク設定で、これがセキュリティインシデントにつながります。ポートを公開し、UFW が有効で SSH 以外をすべて拒否していることを確認しても、サービスがインターネットから到達可能なままになることがあります。
sudo ufw status
curl http://203.0.113.10:8080UFW ではポートがブロックされていると表示されます。しかし、curl はページを返します。故障ではありません。Docker は独自のアドレス変換ルールと転送ルールを iptables に直接書き込みます。公開されたコンテナポート宛てのトラフィックは、ホストに配信されるのではなくコンテナへ転送されます。そのため、ローカル宛てのトラフィックを UFW が管理するチェーンを通過しません。Docker のルールは UFW のルールより先に照合されます。
簡単な対処は、必要な場所にだけ公開することです。
ports:
- "127.0.0.1:8080:80"これによりホスト側は loopback にバインドされます。そのため、ポートに到達できるのはサーバー自身と SSH トンネル経由の場合だけで、他からは到達できません。公開用のエントリポイントは、80 と 443 を意図的に公開するリバースプロキシの背後に配置します。公開ポートをフィルタリングする必要がある場合の DOCKER-USER チェーンを含む詳しい説明は、Docker が UFW を直接迂回して公開する理由とその修正方法にあります。
4 つのコマンドでデバッグする方法
まず、各コンテナが実際にどのネットワーク上にあるかを確認します。
docker network inspect shop_defaultContainers ブロックには、接続されているすべてのコンテナとそのアドレスが表示されます。その一覧にないサービスは、別のネットワーク上にあるか、host モードで動作しているか、実行されていません。
同じネットワークに接続した使い捨てコンテナから名前解決をテストします。自分のイメージ内にツールを用意する必要はありません。
docker run --rm --network shop_default busybox nslookup db
docker run --rm --network shop_default busybox nc -zv db 5432nslookup が失敗する場合は、名前解決またはネットワーク参加に問題があります。nslookup が成功し、nc が失敗する場合、名前とネットワークは正常で、問題は接続そのものにあります。nc が Connection refused を報告する場合、通常の原因はサービスがそのポートで待ち受けていないことです。または、0.0.0.0 ではなく、コンテナ内部の 127.0.0.1 で待ち受けています。後者は開発用サーバーでよく起こります。修正箇所は Docker ではなく、アプリケーションの bind アドレスです。nc がハングしてタイムアウトする場合は、ファイアウォールルールなど、途中でトラフィックを破棄しているものを確認します。一時停止中のコンテナや応答していないコンテナも確認してください。
Docker のバグに見える別の障害もあります。コンテナ同士は通信できるのに、オフィスネットワークや VPN ネットワーク上のマシンへ接続できない場合、Docker のサブネットがそのネットワークと重複している可能性があります。Docker はデフォルトで 172.17.0.0/16 から割り当てます。/etc/docker/daemon.json でプールを変更します。
{
"default-address-pools": [
{ "base": "10.200.0.0/16", "size": 24 }
]
}その後、sudo systemctl restart docker を実行して、影響を受けるネットワークを再作成します。既存のネットワークは、作成時のサブネットを保持するためです。
FAQ
コンテナ間でサービス名を使って通信できないのはなぜですか?
同じネットワーク上にありません。Compose はすべてのサービスを自動的に <project>_default に接続します。ただし、サービスに networks: リストを追加すると、そのリストがそのサービスに設定されるネットワークの完全な一覧になり、デフォルトネットワークは暗黙的に追加されなくなります。docker network inspect <network> を実行し、両方のコンテナが Containers ブロックに表示されることを確認してください。また、どちらのサービスも network_mode: host を使用していないことを確認してください。host モードのコンテナは Docker ネットワークに接続されないため、サービス名を解決できません。
あるサービスから別のサービスへ接続する場合、ポートを公開する必要がありますか?
いいえ。Compose ネットワーク上では、各コンテナのすべてのポートに、そのネットワーク上の他のコンテナから到達できます。ports: は、Docker の外部からのネットワークトラフィックにコンテナを公開する場合にだけ使用します。expose: はドキュメントとしての指定です。データベースのポートを公開するのは、よくあるうえに危険な運用です。データベースがサーバーの公開インターフェースで待ち受けることになるためです。
bridge と host のネットワーク機能の違いは何ですか?
bridge では、コンテナに独自のネットワーク名前空間と仮想スイッチ上のアドレスが割り当てられます。コンテナ間の名前解決と、送信トラフィックの自動変換も行われます。host では、コンテナがホストのネットワークスタックを直接使用します。そのため、個別のアドレス、サービス名による名前解決、ポート公開、ホスト上の他の待ち受けとの分離はありません。デフォルトは bridge であり、プロセスがホストのインターフェースを必要とする場合を除き、bridge を使用するのが適切です。
異なる Compose ファイルに定義されたコンテナを接続するにはどうすればよいですか?
docker network create edge で共有ネットワークを作成し、両方のファイルで external: true を指定して、通信が必要なサービスをそのネットワークに接続します。Compose はそのネットワークを作成も削除もしません。作成手順を省略すると、Compose は起動を拒否し、外部ネットワークとして宣言されているものの見つからないと報告します。
UFW でポートをブロックしているのに、コンテナがインターネットから到達可能なのはなぜですか?
公開ポートは、Docker が iptables に追加する転送ルールによって処理されるためです。これらのルールは UFW のルールより先に評価されます。また、転送トラフィックは UFW がフィルタリングするチェーンを通過しません。"127.0.0.1:8080:80" でホスト側を loopback にバインドし、外部公開するサービスはポート 80 と 443 のリバースプロキシの背後に配置してください。