Docker Composeで本番環境を運用する際の落とし穴と最適化

Docker Composeで本番環境を運用する際の落とし穴と最適化

1.2GBのDockerイメージを本番環境へデプロイするたび、僕は心の中で「やめろ、もう回線をこれ以上消費するな」と祈っている。

この巨大なイメージが抱えるのは、単なるストレージの無駄ではない。デプロイ時間の増大、ネットワーク転送コストの肥大化、そして何よりビルドツールや不要なライブラリを含めることによる脆弱性の拡大だ。コンテナは軽量であるべきだという鉄則を無視した結果、本番環境で「デプロイ待ち」という名の無駄な時間が積み上がっていく。

ここでは、泥臭い試行錯誤の末に辿り着いた、実戦で通用するDocker Composeの最適化手法を共有する。


1. マルチステージビルドの真価:依存関係の「断捨離」

なぜビルドツールを本番に持ち込んではいけないのか。それは、`gcc`や`python`、`npm`といったツールチェーン自体が攻撃対象になり得るからだ。マルチステージビルドは単なる軽量化ではない。「実行環境に必要なものだけを抽出する」というセキュリティ要件の具現化だ。

# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build && npm prune --production # 実行時不要なdevDependenciesを削除

# Stage 2: Runtime
FROM node:18-alpine
WORKDIR /app
# ビルド済み成果物と最小限の依存関係のみをコピー
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/main.js"]

さらに、ビルド時間を短縮するなら`Docker Buildx`のキャッシュ機能を活用せよ。`--cache-from`や`--cache-to`をGitHub Actionsに組み込むだけで、変更のあったレイヤーだけを再ビルドし、CIの時間は劇的に短縮される。


2. Docker Desktopの同期速度問題:`virtiofs`への転換

開発効率を語る上で避けて通れないのが、ファイル同期の遅延だ。`volumes`でローカルをマウントすると、特に大規模プロジェクトではホットリロードが数秒遅れる。

かつては`delegated`オプションでマウントの整合性を犠牲にして速度を稼いでいたが、現在はDocker Desktopの設定で「VirtioFS」を選択するのが正解だ。 従来のgRPC FUSEと比較して、ファイルI/Oのパフォーマンスが段違いに向上している。

# docker-compose.yml
services:
  web:
    build: .
    volumes:
      - .:/app
      - /app/node_modules # コンテナ側の依存関係を保護
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src

※最近のDocker Compose V2なら、`watch`モードを使うことでより直感的なホットリロードが実現できる。


3. 環境切り替えの黄金律:Composeオーバーライド

「開発環境では動くのに、本番環境では設定が足りなくて死ぬ」という事故を未然に防ぐには、設定ファイルを分離するのが鉄則だ。

  • `docker-compose.yml`: ベース構成
  • `docker-compose.prod.yml`: 本番用の最適化(レプリカ数、リソース制限、バックオフ設定)
# docker-compose.prod.yml
services:
  web:
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '0.50'
          memory: 512M
    restart: always

実行時は `docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d` と叩く。これだけで、本番環境特有の「うっかり設定忘れ」を環境差分として管理できる。


4. ネットワークとトラブルシューティングの極意

Docker Composeのネットワークでハマった時、多くのエンジニアはコンテナを再起動して祈る。だが、真の解決策は「コンテナ内に入って疎通を確認する」ことだ。

# コンテナ内のネットワーク状況を確認する
docker compose exec web sh -c "apk add --no-cache curl && curl -v http://db:5432"

もし疎通が取れないなら、コンテナ間のDNS解決が失敗しているか、ポートが公開されていない。`docker network inspect`でIPアドレスを確認し、泥臭くパケットの通り道を探れ。魔法はない。あるのはコマンドとログだけだ。


最後に:最適化は「未来の自分」への手紙だ

Dockerの最適化は、地味で、誰にも褒められない作業だ。しかし、デプロイが30秒で終わる環境と、10分かかる環境。どちらが精神衛生上良いかは言うまでもない。

技術的な負債は、放置すれば利子がついて返ってくる。今日削った100MBのイメージと、数分短縮したビルド時間は、明日、急な緊急修正が必要になった時に、必ず君の背中を助けてくれるはずだ。

コーヒーはもう冷めた。さあ、次はどの無駄を削ろうか。