Dockerでアプリは動かせるようになったが,「コンテナ同士がどう通信しているのか」「なぜこのポートが外から見えるのか」を説明できるだろうか.Dockerネットワークの理解不足は,繋がらないトラブルと,意図しない公開という事故の両方を生む.

とくに危険なのが,Dockerがファイアウォール(ufw)を迂回してポートを外部公開する挙動だ.これを知らないと,「閉じたはずのDBがコンテナでは全世界に公開されていた」という重大事故につながる.

この記事は,VPSでDockerを使う個人開発者,コンテナのネットワークを雰囲気で扱っているエンジニアに向けている.ドライバの種類からサービス名解決,安全なポート公開,ネットワーク分離までを解説する.

読み終えたとき,あなたはコンテナ間通信を意図して設計し,外部公開を安全に制御し,繋がらないトラブルを自力で切り分けられるようになっている.

なぜDockerネットワークの理解が必要か

コンテナはそれぞれ独立したネットワーク空間を持つ.アプリとDBを別コンテナにしたとき,どうやって互いに通信するのか── これを理解していないと,「アプリからDBに繋がらない」で延々と悩む.

そして最大の落とし穴がポート公開の挙動だ.Dockerの-p 5432:5432は,何気なく書くとDBを全世界に公開してしまう.ufwで閉じたつもりでも,Dockerがそれを迂回する.

ネットワークを正しく設計すれば,必要なコンテナ同士だけが通信でき,外部には必要なものだけを公開するという安全な構成が作れる.これはセキュリティと安定性の両方に効く.

Dockerネットワークの理解は,コンテナ構成を「なんとなく動く」から「意図して安全に動かす」へ引き上げる.

ネットワークドライバ ― bridge・host・none

Dockerのネットワークには種類(ドライバ)がある.個人開発でまず理解すべきはbridgeだ.残りは特殊用途と割り切ってよい.

ドライバ特徴用途
bridgeDocker内の仮想ネットワーク(既定)通常のコンテナ間通信
hostホストのネットワークを直接使う高性能が要る特殊ケース
noneネットワークなし完全に隔離したいとき

ほとんどのケースはbridgeで事足りる.そして後述するが,bridgeには「デフォルトbridge」と「ユーザー定義bridge」という違いがあり,この差がサービス名での通信ができるかを決める重要ポイントになる.

ユーザー定義ネットワーク ― サービス名で通信する

コンテナ間通信の鍵がユーザー定義ネットワークだ.自分でネットワークを作り,そこにコンテナを所属させると,コンテナ名で互いに名前解決できる(IPアドレスを意識せずに通信できる).

デフォルトのbridgeネットワークでは名前解決ができず,IPアドレス指定が必要になる(しかもIPは起動ごとに変わりうる).必ずユーザー定義ネットワークを使うのが現代の作法だ.

ユーザー定義ネットワークでの通信

# ネットワークを作成
docker network create appnet
# コンテナを同じネットワークに所属させる
docker run -d --name db --network appnet postgres:16
docker run -d --name api --network appnet myapi

# api コンテナからは ホスト名 "db" で接続できる
# 例: postgres://user:pass@db:5432/mydb

ポイントは,apiコンテナからDBにdb:5432(コンテナ名:ポート)で接続できることだ.IPアドレスを一切意識せず,名前で繋がる.これがユーザー定義ネットワークの威力で,構成が再現可能で読みやすくなる.

composeのネットワーク ― 自動で繋がる

docker composeを使うと,同じcompose内のサービスは自動的に1つのネットワークに所属し,サービス名で通信できる.手動でネットワークを作る必要すらない.

composeでのサービス間通信

services:
  api:
    build: .
    environment:
      DATABASE_URL: postgres://app:pass@db:5432/mydb  # "db"で繋がる
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: pass
# api から db へは サービス名 "db" で到達できる(自動)

composeではサービス名がそのままホスト名になる.上記ならapiからdbという名前でPostgreSQLに繋がる.これがcomposeが個人開発で強力な理由の一つだ.接続文字列にコンテナ名を書くだけで,複数コンテナがきれいに連携する.

ポート公開の正しい理解 ― localhostバインドで守る

ここが最重要かつ事故の多発地帯だ-p 5432:5432(またはcomposeのports)は,そのポートを全インターフェース(0.0.0.0)で公開し,外部からアクセス可能にする.ufwで閉じていてもDockerが迂回する.

localhostバインドで外部公開を防ぐ

# 危険: DBを全世界に公開してしまう
#   ports: ["5432:5432"]

# 安全: localhostのみに公開(同じVPS内からだけ)
services:
  db:
    ports:
      - "127.0.0.1:5432:5432"   # 外部からは見えない

鉄則は明快だ.外部に公開する必要があるのはリバースプロキシ(80/443)だけ.DB・Redis・内部APIはそもそもportsで公開しない(同じネットワーク内のコンテナからはサービス名で繋がるので公開不要)か,どうしてもホストから触りたい場合のみ127.0.0.1:を付けてlocalhostに限定する.これを徹底すれば,意図しない公開事故は起きない.

「公開」と「コンテナ間通信」は別物

重要な区別がある.同じネットワーク内のコンテナ同士は,portsで公開しなくても通信できるportsは「ホスト(外部)に晒すかどうか」の設定だ.DBはアプリコンテナから繋がればよいので,外部公開(ports)は不要.この理解が,安全な構成の核になる.

ネットワーク分離 ― フロントとバックを分ける

セキュリティを高める設計として,ネットワークを分離する手法がある.「外部に面するフロント」と「内部だけのバックエンド」を別ネットワークにし,DBを外部経路から隔離する.

フロント/バックのネットワーク分離

services:
  proxy:
    networks: [frontend]
    ports: ["443:443"]
  api:
    networks: [frontend, backend]   # 両方に所属
  db:
    networks: [backend]             # backendのみ=外部から隔離
networks:
  frontend:
  backend:

この構成では,DBはbackendネットワークにのみ所属し,外部に面するproxyとは直接繋がらない.apiだけが両方に所属して橋渡しをする.万一proxyが侵害されても,DBへの直接経路が無いため被害を抑えられる.多層防御のネットワーク版だ.

トラブルシュート ― 繋がらない時の切り分け

コンテナ間で繋がらないときは,ネットワーク所属とDNS解決を確認する.

ネットワークのデバッグ

docker network ls                       # ネットワーク一覧
docker network inspect appnet           # 所属コンテナを確認
# コンテナ内から疎通確認
docker exec -it api ping db
docker exec -it api getent hosts db     # 名前解決の確認

「アプリからDBに繋がらない」ときの定石は,(1)両者が同じネットワークに所属しているか(network inspect) (2)サービス名で名前解決できるか(getent hosts) (3)接続先のホスト名・ポートが正しいかを順に確認することだ.デフォルトbridgeを使っていて名前解決できない,というのが初学者の典型的な詰まりどころだ.

補論:安全なコンテナ構成は「戻せるVPS」で身につく

Dockerネットワークの設計,とくにポート公開とufwの関係は,実際に手を動かして「外から見えるか」を確認しないと腹落ちしない.そして設定を試す過程では,意図せず公開してしまう失敗も起きる.戻せる環境での練習が安全だ.

高速NVMe・50種類以上のOSテンプレートに対応した国内VPS─シン・VPS─ はスナップショットとコンソールを備え,NVMe SSDでコンテナの起動も軽快なため,ネットワーク構成を組んでは壊し,外部からのポートスキャンで公開状態を確認するといった検証を安全に繰り返せる.Docker+ufwの競合事故からもスナップショットで復旧できる.

コンテナ構成のアプリを独自ドメインで公開するなら,取り扱い400種類以上のドメイン取得サービス─ムームードメイン─ でドメインを取得し,公開するのはリバースプロキシ(443)だけ・DBやRedisは内部ネットワークに隔離,という安全な構成を組もう.

よくある質問(FAQ)

Q1.コンテナ同士が繋がらない

同じユーザー定義ネットワークに所属しているかdocker network inspectで確認する.デフォルトbridgeではサービス名で名前解決できないため,必ずユーザー定義ネットワーク(composeなら自動)を使う.

Q2.DBを-pで公開して大丈夫?

危険だports: ["5432:5432"]はDBを全世界に公開し,ufwも迂回する.同じネットワーク内のアプリからはサービス名で繋がるので公開は不要.ホストから触りたい場合のみ127.0.0.1:5432:5432に限定する.

Q3.Dockerがufwを無視すると聞いた

事実だ.Dockerはports公開時に独自のルールを挿入し,ufwを迂回する.対策は不要なポートを公開しない・公開はlocalhostバインドに限定・外部公開はリバースプロキシ経由にすること(ファイアウォール記事も参照).

Q4.公開とコンテナ間通信の違いは?

コンテナ間通信は同じネットワーク内ならports不要で可能portsは「ホスト(外部)に晒すか」の設定だ.DBはアプリから繋がればよいので外部公開しない,が安全の基本だ.

Q5.ネットワーク分離は個人開発でも必要?

必須ではないが,DBを外部経路から隔離できるため安全性が上がる.フロント/バックを分け,DBをbackendのみに所属させれば,proxyが侵害されてもDBへの直接経路が無い.余裕があれば取り入れたい.

Q6.host networkは使うべき?

通常は不要だ.bridgeで十分で,hostはネットワーク分離の利点を捨てるため特殊な高性能要件のときだけ使う.個人開発はユーザー定義bridge(composeの自動ネットワーク)を基本にする.

まとめ ― ネットワークを制すれば「安全に繋がる」が作れる

Dockerネットワークの要点は,ユーザー定義ネットワークでサービス名通信し,ポート公開はlocalhostバインドで絞り,DBやRedisは外部公開しないことだ.これでコンテナ間は繋がり,外部には必要なものだけが公開される.

今日やるべきことは,自分のcomposeでportsを見直し,DBやRedisを外部公開していないか確認すること.公開していれば127.0.0.1:を付けるか,そもそも公開を外す.

「なんとなく動く」から「意図して安全に動かす」へ.戻せるVPSがあれば,ネットワーク構成も恐れず設計できる.