「DBが消えた」とSlackで泣く前に。Docker Compose開発環境を「爆速・堅牢」にするための全技術

「DBが消えた」とSlackで泣く前に。Docker Compose開発環境を「爆速・堅牢」にするための全技術

「おい、データベースが消えたぞ」。

開発環境を立ち上げ直した瞬間、チームのSlackに流れた絶望のメッセージ。原因は俺が適当に組んだ`docker-compose.yml`のVolume設定だった。あの時の冷や汗と、数時間かけてデータを復旧した徒労感。あれはまさに地獄の入り口だ。

個人開発でDockerを使い始めた頃の俺は、「とりあえず全部マウントしとけばええやろ」という思考停止状態だった。だが、Dockerというツールは、その「適当さ」を容赦なく破壊してくる。

本稿では、当時の俺がなぜ地獄を見たのか、そしてそれをどう解決し、開発体験(DX)を劇的に向上させたのかを、技術的な裏付けとともに共有する。


1. なぜ「ホストマウント」はDBにとって毒なのか

かつて俺は、DBのデータディレクトリをホストのディレクトリに直接マウントしていた。Docker Desktopのファイル共有機能(gRPC FUSE)を使い、Macのパスをコンテナに流し込んでいたんだ。

これが全ての元凶だ。

致命的なオーバーヘッド

macOSのファイルシステムとDockerのコンテナ間で、大量の小さなI/Oが発生するDBのデータ書き込みは、VirtioFSやgRPC FUSEの層を介すると劇的に遅くなる。インデックス生成のたびにCPUが爆死するのは当然だ。

さらに、Docker Desktopの設定が競合し、コンテナを`down`するタイミングとホスト側のメタデータ同期が重なると、DBファイルは一瞬で破損する。

解決策はシンプルだ。DBのデータ領域は「Named Volume」に逃がせ。

services:
  db:
    image: postgres:15-alpine
    volumes:
      - db_data:/var/lib/postgresql/data # ホストパスを指定しない!
    environment:
      POSTGRES_DB: dev_db
      POSTGRES_PASSWORD: password

volumes:
  db_data: # Docker管理下に置くことでI/Oオーバーヘッドを排除

Named Volumeは、ホストOSのファイルシステム層を介さないため、Linuxネイティブに近いI/Oパフォーマンスを発揮する。これでDBの起動速度は体感で3倍以上、安定性は段違いになった。


2. 「起動したのに繋がらない」を撲滅するHealthcheck

`depends_on`で`service_started`を待つだけでは不十分だ。コンテナのプロセスが立ち上がった瞬間にアプリが接続を試みれば、DB側の初期化が間に合わず即死する。

「起動→接続エラー→手動リトライ」の無限ループを断ち切るには、`healthcheck`を定義し、DBの準備完了をコンテナ自身に判定させる必要がある。

services:
  db:
    image: postgres:15-alpine
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
  
  app:
    build: .
    depends_on:
      db:
        condition: service_healthy # DBがクエリを受け付けられるまで待機

これで、アプリは「DBが本気で準備できた瞬間」にのみ起動するようになる。


3. マイグレーション地獄からの脱却

複数人開発で最も消耗するのが、マイグレーションの食い違いだ。これを防ぐには、「アプリケーション起動時に必ずマイグレーションを走らせる」という強制力をスクリプトに組み込むべきだ。

#!/bin/sh
# entrypoint.sh

# DBの疎通確認を二重で担保する
echo "Waiting for database..."
until nc -z db 5432; do
  sleep 1
done

echo "Running migrations..."
npx prisma migrate deploy

# アプリケーション起動
exec "$@"

`prisma migrate deploy`を使うことで、CI環境やローカル環境での事故を未然に防ぐ。手動実行という「人間によるミス」を前提にした構成こそが、最も堅牢な設計なのだ。


4. `node_modules`をDockerに閉じ込める意味

初心者ほどやりがちなのが、ホスト側の`node_modules`をコンテナにマウントすることだ。

# 悪い例
volumes:
  - ./node_modules:/app/node_modules

これは地雷だ。ホストのOS(macOS)でビルドされたバイナリがコンテナ(Linux)に混入し、謎の挙動を引き起こす。さらに、ホストとコンテナ間でパッケージの競合が発生すれば、「ホストでは動くのに」というエンジニアが最も嫌うデバッグ作業が確定する。

`node_modules`は匿名ボリュームで隠蔽せよ。

services:
  app:
    volumes:
      - .:/app
      - /app/node_modules # ホストのnode_modulesを上書きして隔離

これにより、コンテナ内の環境はコンテナ内で完結する。依存関係のクリーンさは、そのまま開発効率に直結するのだ。


まとめ:DXへの投資をサボるな

Docker Composeは、単に「環境を立ち上げるツール」ではない。「環境差異というノイズを極限まで排除し、コードを書くことだけに集中するための投資」だ。

1. Named VolumeでI/Oを最適化し、データ破損を防ぐ。
2. Healthcheckで起動の順序を厳密に制御する。
3. Entrypointでマイグレーションを自動化し、ヒューマンエラーを消す。
4. Volumeによる隔離で、ホスト環境との依存関係の混入を防ぐ。

今の俺の環境は、コマンド一発、30秒で開発可能な状態になる。もし君が、`docker-compose up`した後に「DB起動待ち」でコーヒーを淹れに行っているなら、それは構成を見直すべきサインだ。

ローカル環境の技術的負債は、将来の自分への借金。今すぐ自動化し、本来書くべきコードに集中しようぜ。