Docker

Docker Composeでデータベースを外部に公開しない設定

Docker データベース セキュリティ

Docker Composeで
データベースを外部に公開しない設定

Docker Composeで構築したWebアプリケーション、データベースのポートを外部に公開していませんか?
PostgreSQL、MySQL、Redisなどのデータベースは、外部に公開する必要がないサービスです。
この記事では、データベースを安全に内部ネットワークだけに閉じる3つの方法を解説します。

こんな人向けの記事です

  • Docker ComposeでWebアプリ + データベースを運用している
  • docker-compose.yml でデータベースに ports を書いている
  • データベースを外部に公開したくないが、どう設定すればいいかわからない

なぜデータベースを外部に公開してはいけないのか

データベースが外部に公開されていると、以下のリスクがあります。

リスク内容
ブルートフォース攻撃パスワードを総当たりで試行され、突破される
脆弱性の悪用データベースの既知の脆弱性を突かれて侵入される
データ漏洩ユーザー情報や機密データが外部に流出する
ランサムウェアデータを暗号化され、身代金を要求される
実際に起きている被害
インターネット上に公開されたデータベースは、自動スキャンツールによって数分以内に発見されます。Shodan等の検索エンジンには、ポート5432(PostgreSQL)や3306(MySQL)が公開されているサーバーが大量にリストされています。

Step 1現状を確認する

まず、あなたの docker-compose.yml を確認しましょう。以下のようにデータベースに ports が設定されていたら危険です。

docker-compose.yml(危険な例)
services:
  web:
    build: .
    ports:
      - "80:80"

  db:
    image: postgres:16
    ports:
      - "5432:5432"       # 全世界に公開されている
    environment:
      POSTGRES_PASSWORD: mypassword

  redis:
    image: redis:7
    ports:
      - "6379:6379"       # 全世界に公開されている

サーバー上で実際にポートが公開されているか確認します。

サーバー
ss -tlnp | grep -E '(5432|3306|6379|27017)'
LISTEN 0 4096 0.0.0.0:5432 0.0.0.0:* users:(("docker-proxy"...)) LISTEN 0 4096 0.0.0.0:6379 0.0.0.0:* users:(("docker-proxy"...))

0.0.0.0 にバインドされていたら、外部から直接アクセス可能な状態です。すぐに対策しましょう。

Step 2方法1: ports を削除する(推奨)

最もシンプルで安全な方法です。データベースの ports を削除するだけです。

修正前(危険)

docker-compose.yml
services:
  db:
    image: postgres:16
    ports:
      - "5432:5432"
    environment:
      POSTGRES_PASSWORD: mypassword

修正後(安全)

docker-compose.yml
services:
  db:
    image: postgres:16
    # ports は書かない(Docker内部ネットワークのみ)
    environment:
      POSTGRES_PASSWORD: mypassword
なぜ ports を書かないだけで安全なのか?
Docker Composeの同一ファイル内のサービスは、自動的に同じ内部ネットワークに接続されます。web コンテナからは db:5432 でアクセスできますが、ホストマシンや外部からはアクセスできません。ports はホストのポートにマッピングする設定なので、書かなければ外部に公開されません。

完全な構成例

docker-compose.yml
services:
  web:
    build: .
    ports:
      - "80:80"           # Webサーバーのみ外部公開
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16
    # ports なし = 外部非公開
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD: mypassword
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myapp"]
      interval: 5s
      timeout: 3s
      retries: 5

  redis:
    image: redis:7-alpine
    # ports なし = 外部非公開
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:

この設定では、web コンテナからは db:5432redis:6379 に接続できますが、外部からは一切アクセスできません。

Step 3方法2: 127.0.0.1 にバインドする

開発時にホストマシンからデータベースに直接接続したい場合(GUIツールでの確認等)、127.0.0.1 にバインドする方法があります。

docker-compose.yml
services:
  db:
    image: postgres:16
    ports:
      - "127.0.0.1:5432:5432"  # ローカルホストからのみ接続可
    environment:
      POSTGRES_PASSWORD: mypassword
127.0.0.1 を指定するとどうなる?
127.0.0.1:5432:5432 と書くと、サーバー自身(localhost)からのみ 5432 ポートにアクセスできます。外部ネットワークからは到達できません。ホストマシンから psql -h 127.0.0.1 でDBに接続しつつ、外部には非公開にできます。
本番環境では方法1を推奨
本番環境ではデータベースに直接接続する必要はないため、ports 自体を書かない方法1が最も安全です。ホストからの接続が必要な開発環境でのみ、この方法を使いましょう。

Step 4方法3: internal ネットワークを使う

より厳密に制御したい場合、Docker の internal ネットワークを使う方法があります。internal: true を指定したネットワークは、外部との通信が完全に遮断されます。

docker-compose.yml
services:
  web:
    build: .
    ports:
      - "80:80"
    networks:
      - frontend         # 外部アクセス用
      - backend           # DB接続用

  db:
    image: postgres:16
    networks:
      - backend           # 内部ネットワークのみ
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: mypassword

networks:
  frontend:               # 外部通信可能
  backend:
    internal: true        # 外部通信を完全遮断

volumes:
  postgres_data:
internal ネットワークの特徴
internal: true のネットワークに接続されたコンテナは、同じネットワーク内のコンテナとのみ通信できます。外部ネットワーク(インターネット)への接続も遮断されるため、データベースコンテナからの外部通信(例えば、乗っ取られた場合のデータ送信)も防止できます。
方法安全性用途
方法1: ports を削除高いほとんどの本番環境で推奨
方法2: 127.0.0.1 バインド中程度ホストからDB接続が必要な開発環境
方法3: internal ネットワーク最も高い厳格なセキュリティが求められる環境

Step 5環境変数でパスワードを管理する

データベースのパスワードを docker-compose.yml に直接書くのは危険です。Gitにコミットされると、パスワードが流出します。.env ファイルを使って管理しましょう。

.env ファイルを作成する

.env
POSTGRES_DB=myapp
POSTGRES_USER=myapp
POSTGRES_PASSWORD=ここに強力なパスワード

docker-compose.yml から参照する

docker-compose.yml
services:
  db:
    image: postgres:16
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

.gitignore に追加する

.gitignore
.env
.env を Git にコミットしない
.env ファイルは必ず .gitignore に追加してください。代わりに .env.example をコミットして、必要な環境変数のテンプレートを共有しましょう。

Step 6設定を検証する

変更を反映する

サーバー
docker compose down
docker compose up -d

ポートが公開されていないことを確認する

サーバー
ss -tlnp | grep -E '(5432|3306|6379|27017)'

方法1(ports 削除)の場合、何も表示されなければ成功です。データベースのポートはホストにマッピングされていません。

Webアプリからデータベースに接続できることを確認する

サーバー
# Webコンテナからデータベースに接続できるか確認
docker compose exec web python -c "
import psycopg2
conn = psycopg2.connect(host='db', dbname='myapp', user='myapp', password='mypassword')
print('接続成功')
conn.close()
"
接続成功

外部からアクセスできないことを確認する

ローカル(自分のPC)
# タイムアウトまたは接続拒否になればOK
psql -h サーバーのIPアドレス -p 5432 -U myapp -d myapp
psql: error: connection to server at "xxx.xxx.xxx.xxx", port 5432 failed: Connection refused

Connection refused または応答なし(タイムアウト)になれば、外部からのアクセスは遮断されています。

まとめ

Docker Composeでデータベースを安全に運用するためのポイントをまとめます。

  1. データベースに ports を書かない — 同一Composeファイル内のサービスは、ports なしでもサービス名で接続できる
  2. 開発時に直接接続が必要なら 127.0.0.1 にバインドする — 外部には公開されない
  3. 厳格なセキュリティには internal ネットワークを使う — DBからの外部通信も遮断できる
  4. パスワードは .env ファイルで管理する — Gitには .env.example のみコミットする

「とりあえず ports でポートを公開しておく」という習慣は、本番環境では非常に危険です。データベースは外部に公開する必要がないサービスであることを忘れずに、必ず内部ネットワークに閉じた設定にしましょう。