Docker Composeの爆速化:なぜお前の環境は「重い」のか?

Docker Composeの爆速化:なぜお前の環境は「重い」のか?

「Docker Composeのビルドが遅い」「ホットリロードのたびにPCが離陸しそうになる」。そんな地獄のような開発体験を、いつまで放置するつもりだ?

ネットに転がっている「Dockerfileの書き方」をなぞるだけでは、この問題は解決しない。Dockerの仕組みを理解せず、ただマウントして動かしているだけなら、それは「開発環境」ではなく「ただの重い箱」だ。

本質は、「Dockerのファイルシステムとどう付き合うか」という一点に集約される。今日でその泥沼から脱出しよう。


1. マウントの「落とし穴」を埋める

開発環境でホットリロードが重い最大の原因は、Dockerのファイルシステム(VirtioFSやgRPC FUSE)への過剰な負荷にある。ローカルの全ファイルをマウントし、`node_modules`までホストと同期させようとするのは、Dockerに対する「自爆テロ」に近い。

匿名ボリュームという「隔離壁」

ホスト側の`node_modules`をコンテナにマウントしてはいけない。ローカルのゴミファイルやOS依存のバイナリがコンテナ内のプロセスと干渉し、ファイル監視(inotify)がパンクするからだ。

修正前(死ぬコード):

volumes:
  - .:/app # これをやるとnode_modulesまで共有され、監視負荷が爆増する

修正後(最適解):

services:
  app:
    build: .
    volumes:
      - .:/app
      - /app/node_modules # 匿名ボリュームでローカルの干渉を遮断する

これで、コンテナ内のライブラリはコンテナ内で完結する。たったこれだけで、ファイル変更時の反応速度は劇的に変わる。


2. マルチステージビルドで「ビルドキャッシュ」を支配する

マルチステージビルドを単なる「イメージ軽量化の道具」だと思っているなら、それは大きな誤解だ。真の価値は「ビルドプロセスの依存関係の切り分け」にある。

開発環境と本番環境でDockerfileを分ける必要はない。一つのDockerfileをステージ分けし、`docker-compose.yml`からターゲットを指定するだけでいい。

Dockerfileの再構成

# ステージ1: 依存関係のインストール(ここが一番重い)
FROM node:18-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm install

# ステージ2: 開発環境用(ホットリロード用)
FROM node:18-alpine AS runner
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
CMD ["npm", "run", "dev"]

docker-compose.ymlでのターゲット指定

services:
  app:
    build:
      context: .
      target: runner # ここでステージを切り替える
こうすれば、`package.json`を書き換えない限り、`deps`ステージはキャッシュされ続ける。コードの変更が反映されるのは`runner`ステージだけ。これが爆速ビルドの正体だ。

3. .dockerignoreをサボるな:ビルドコンテキストの闇

俺が過去に一番ハマったのは、`.dockerignore`の放置だった。ある日、プロジェクトルートに数GBの`.git`ディレクトリや動画素材を放置したままビルドを回し、「なんで毎回Dockerデーモンへの転送に1分もかかるんだ?」と頭を抱えた。

`docker build`を実行した瞬間、Dockerはプロジェクト内の全ファイルを「コンテキスト」としてデーモンに転送する。不要なファイルが含まれていれば、当然その転送時間はビルド時間に加算される。

必須の`.dockerignore`:

.git
node_modules
dist
*.log
.DS_Store
tmp

この一行を書くか書かないかで、ビルドのスタートラインが数秒単位で変わる。まずはここを見直せ。


明日から導入すべき「爆速化チェックリスト」

環境構築に時間を溶かすのはもう終わりにしよう。以下の3項目を今すぐ適用してくれ。

項目アクション効果
ボリューム隔離`/app/node_modules` を匿名ボリューム化ファイル監視負荷の激減
ターゲット分離マルチステージビルドで `target` を定義ビルドキャッシュの最適化
コンテキスト削減`.dockerignore` を徹底的に書く初回ビルド・転送時間の短縮

最後に

複雑な同期ツールを入れるのは、これら全てをやった後の「最終手段」でいい。道具に頼る前に、Docker本来の仕組みを使い倒せ。

エンジニアの時間は、環境設定のデバッグではなく、プロダクトの価値を最大化するために使うものだろ? 今日紹介した手法は、特別なツールもコストも不要だ。今すぐ設定ファイルを書き換えて、その「軽さ」を体感してほしい。