状況
深夜2時、アプリコンテナが数秒おきにこのエラーを吐き続けている:
dial tcp: lookup db on 127.0.0.1:53: no such host
docker-compose.yml には db という名前のサービスがある。アプリが明らかにそこへ接続しようとしている。DBコンテナは起動中だ — 確認済みだ。では何が起きているのか?
端的に言うと、アプリコンテナが 127.0.0.1:53 をDNSサーバーとして使っている。これはホストマシンのループバックリゾルバーであり、DockerのインターナルDNSではない。Dockerのサービス名前解決は、127.0.0.11 にある独自の組み込みDNSを経由する。ホストのリゾルバーは db というホスト名を知らないため、即座に失敗する。
これはコンテナがDockerマネージドネットワークに属していない場合、またはDNS設定が壊れた場合に発生する。いずれにしてもシステムリゾルバーにフォールバックするが、システム側は db が何を意味するか一切知らない。
デバッグ手順
ステップ1:両コンテナが同じネットワーク上にいるか確認する
十中八九、これが原因だ。以下を実行する:
docker network ls
docker inspect <your_container_name> | grep -A 20 '"Networks"'
Networks セクションを確認する。アプリコンテナが bridge(デフォルトのレガシーネットワーク)で、DBが myapp_default にいる場合、両者は互いに隔離されている — 異なるネットワーク間ではサービス名は解決できない。
ステップ2:DockerのインターナルDNSが使われているか検証する
アプリコンテナの中に入って確認する:
docker exec -it <app_container> cat /etc/resolv.conf
正常なDockerコンテナは以下を表示するはずだ:
nameserver 127.0.0.11
options ndots:0
nameserver 127.0.0.1 や 8.8.8.8 のようなパブリックリゾルバーが表示される場合、DockerのDNSが機能していない。これはコンテナが --network host で実行されているか、ユーザー定義ネットワークにアタッチされていない場合に発生する。
ステップ3:名前解決を手動でテストする
docker exec -it <app_container> nslookup db
docker exec -it <app_container> ping db
nslookup が失敗するがDBコンテナは起動中?ネットワークのアタッチ問題だ。アプリの設定問題ではない。
解決策
解決策1:両サービスを同じ名前付きネットワークに配置する(Docker Compose)
まずここから始める — これでケースの90%が解決する。docker-compose.yml に記述する:
services:
app:
build: .
networks:
- backend
environment:
DATABASE_URL: postgres://user:pass@db:5432/mydb
db:
image: postgres:15
networks:
- backend
networks:
backend:
driver: bridge
両サービスが同じComposeファイルに存在し、明示的なネットワーク設定がない場合、Docker Composeは自動的に <project>_default という共有ネットワークを作成し、全てのサービスをそこに配置する。サービス名は自動的に解決される。追加設定は不要だ。
このエラーが発生するのは以下のケースだ:
- 一方のサービスには
networks:を設定したが、もう一方を忘れた場合 - 一方のコンテナをComposeではなく
docker runで手動起動した場合 - 異なるプロジェクトネットワーク上のサービスを持つ複数のComposeファイルがある場合
解決策2:手動起動コンテナをComposeネットワークに接続する
DBがComposeスタックに存在する中、アプリを docker run で起動した場合は接続する:
# ネットワーク名を確認する
docker network ls
# スタンドアロンコンテナを接続する
docker network connect myproject_default <app_container_name>
再起動は不要だ。接続直後からサービス名の解決が機能し始める。
解決策3:--network host をやめる
--network host はコンテナにホストのネットワークスタック全体を共有させる。DockerのインターナルDNSは適用されない。サービス名は一切解決できなくなる。
ホストネットワーキングが本当に必要な場合(rawソケット、ハードウェアアクセス、非常に特殊なポートバインドのシナリオ)を除いて、削除する:
# 誤り
docker run --network host myapp
# 正解 — 名前付きネットワークを使う
docker run --network myproject_default myapp
解決策4:複数のComposeファイル — 共有外部ネットワークを作成する
相互通信が必要な別々のComposeスタックを実行している場合は、共有ネットワークを明示的に定義する:
# DBスタック側(docker-compose.db.yml)
networks:
shared:
name: shared_backend
driver: bridge
# アプリスタック側(docker-compose.app.yml)
networks:
shared:
external: true
name: shared_backend
これで両スタックが同じネットワークを共有する。追加のトリックなしでスタック間のサービス名が解決される。
確認
修正が反映されたか確認するために以下を実行する:
# コンテナが正しいネットワーク上にいるか確認する
docker inspect <app_container> | grep -A 5 '"Networks"'
# Docker DNSがアクティブか確認する
docker exec -it <app_container> cat /etc/resolv.conf
# 表示されるべき内容: nameserver 127.0.0.11
# 名前解決をテストする
docker exec -it <app_container> nslookup db
# 172.18.0.x のような内部IPに解決されるはずだ
nslookup db がIPを返すなら問題ない。アプリでまだエラーが出る場合はアプリコンテナを再起動する — 一部のランタイム(特にGoやJavaのアプリ)はDNS失敗をメモリにキャッシュするため、プロセスが再起動するまで再試行しない。
ヒント:サブネットの衝突をデバッグする
DNSは解決できたが接続がまだ失敗する場合は、サブネットの衝突を確認する。DockerはCIDRレンジを自動的に選択するが、VPNやオフィスネットワークと重複してルーティングが完全に壊れることがある。ネットワークのサブネットを確認する:
docker network inspect <network_name> | grep Subnet
よくある原因:Dockerが 172.17.0.0/16 をデフォルトに選ぶ一方、VPNが同じレンジを使っている場合だ。Composeファイルでカスタムサブネットを固定して衝突を回避する:
networks:
backend:
driver: bridge
ipam:
config:
- subnet: 10.50.0.0/24
どのアドレスがどのサブネットに属するかを調べる際は、ToolCraftのサブネット計算機を使っている — ブラウザベースで、ブロードキャストアドレス、使用可能なレンジ、ワイルドカードマスクを外部通信なしで表示してくれる。
得られた教訓
- サービス名のDNSはユーザー定義ネットワーク上でのみ機能する。 デフォルトの
bridgeネットワークはサービスディスカバリーをサポートしない。Composeでは必ずネットワークに名前を付けること。 docker runとdocker composeの混用は、気づかないうちにこの問題を引き起こす。 手動で起動したコンテナは自動的にComposeネットワークに参加しない。docker network connectで手動接続する必要がある。--network hostは大ハンマーだ。 Docker DNSを完全にバイパスする。ポートバインドの問題の応急処置としてではなく、本当に必要な場合にのみ使うこと。/etc/resolv.confのnameserver行が全てを語る。127.0.0.11はDocker DNSがアクティブでサービス名が機能することを意味する。それ以外はDocker外で解決しようとしており、dbはそこでは無意味だ。

