Dockerのビルドが遅い?お前の「脳内キャッシュ」が腐ってるだけだ

Dockerのビルドが遅い?お前の「脳内キャッシュ」が腐ってるだけだ

「Dockerのビルドに5分かかる」。そう嘆く同僚のPCを覗き込むと、決まって`.dockerignore`は空っぽで、コンテキストには数GBの`node_modules`や`.git`ディレクトリが律儀に詰め込まれている。

いいか、Dockerのキャッシュは魔法じゃない。お前が投げたゴミを、Dockerは律儀にレイヤーとして積み上げているだけだ。ビルドが遅いのはツールのせいじゃない。お前が「レイヤー構造」という概念を理解していないからだ。

今日でその「待ち時間」という名の人生の浪費を卒業しろ。


1. マルチステージビルドは「軽量化」のためじゃない、依存を「断捨離」するためだ

マルチステージビルドを単に「イメージサイズを小さくする手段」だと思っているなら、一生初心者から抜け出せない。真の目的は、ビルド環境と実行環境を物理的に分離し、キャッシュのヒット率を最大化することにある。

特にPython環境なら、`--mount=type=cache`を使わない手はない。これを知らない奴が、毎回`pip install`をゼロから走らせてコーヒーを淹れに行っているのを見ると、正直溜息が出る。

# syntax=docker/dockerfile:1
FROM python:3.11-slim AS builder

WORKDIR /app
# ビルド時の依存関係をキャッシュマウントで高速化
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
COPY . .

CMD ["python", "main.py"]

これを使えば、`requirements.txt`に変更がない限り、依存関係のインストールは一瞬で終わる。コードを1行変えるたびに数分待つ苦行は、これで今日で終わりだ。


2. 「全部乗せ」の呪縛を解く:Profiles活用のススメ

開発中に使うのはWebとDBだけなのに、なぜか`docker-compose.yml`に書かれたRedis、Elasticsearch、Adminerまで全部起動してメモリを食いつぶす……そんな無駄なことは今すぐやめろ。

`profiles`を使えば、必要な時だけコンテナを召喚できる。

services:
  web:
    build: .
    profiles: ["dev"]
  db:
    image: postgres:15
    profiles: ["dev", "test"]
  adminer:
    image: adminer
    profiles: ["debug"] # 必要な時だけ docker compose --profile debug up

CI環境では `--profile test` だけを叩き、ローカルでは `docker compose up`(デフォルトは`dev`のみ)で済ませる。環境ごとに必要なリソースを厳密に制御する。これが「できるエンジニア」の作法だ。


3. CIのキャッシュ戦略:GitHub Actionsで「転送コスト」に殺されないために

ローカルで爆速になっても、CIでこけては意味がない。GitHub Actionsで`docker/build-push-action`を使う際、何も考えずにキャッシュを有効化すると、ネットワーク転送コストでビルド時間以上に待たされるという本末転倒な事態に陥る。

現代のベストプラクティスは `gha` バックエンド一択だ。

- name: Build and push
  uses: docker/build-push-action@v5
  with:
    push: false
    cache-from: type=gha
    cache-to: type=gha,mode=max

ここで重要なのは `mode=max` だ。デフォルトの `min` だとレイヤーキャッシュが不十分で、結局CIのたびにビルドが走る。`max`を指定して、すべてのレイヤーをキャッシュに乗せろ。ただし、巨大なイメージを何個も作ればGitHubのキャッシュ制限に引っかかる。「何でもかんでもキャッシュするな」という引き算の美学も忘れるな。


4. そもそも、お前のアーキテクチャが「ビルド阻害」していないか?

Dockerのせいにしているが、実は「モノリスすぎる巨大なディレクトリ」が最大の敵だったりする。

アンチパターンの筆頭は、「ソースコードの変更が頻繁なファイルを、Dockerfileの早い段階でCOPYしていること」だ。例えば、設定ファイルやログ出力用のディレクトリを`COPY . .`で一番上に置いていないか?

ディレクトリ構成を細分化し、変化しない静的なライブラリや設定を先にCOPYし、最後に変更の多いソースコードをCOPYする。この「レイヤー順序の最適化」を意識するだけで、ビルド時間は劇的に短縮される。


さあ、今すぐやれ

お前が今、この記事を読み終えてやるべきことは明確だ。

1. `.dockerignore`を即座に作成し、`node_modules`, `.git`, `__pycache__`を叩き込め。
2. Dockerfileの`COPY`順序を見直し、レイヤーがキャッシュされる順番になっているか確認しろ。
3. CIのログを見返せ。 Dockerのビルドステップで「CACHED」の文字が何個並んでいるか数えろ。それがお前のエンジニアとしての誠実さの数値だ。

ツールを使いこなせないのは、お前の頭が硬いからだ。Dockerは「怠惰で賢い人間」のために最適化しろ。そうすれば、お前らはもっとマシなコードを書くための貴重な時間を取り戻せるはずだ。

さっさとエディタに戻れ。待ち時間はもう終わりだ。