「Dockerイメージが1GB超えてます」とか平気で言ってくる後輩を前にすると、正直頭が痛くなる。お前の環境はゴミ捨て場かよ。
ローカルの立ち上げに3分もかかるような環境で消耗してるやつ、マジで時間の無駄だから今すぐやめろ。Dockerは「とりあえず動けばいい」で放置すると、肥大化したイメージと遅いビルドに殺される。今回は、現場で生き残るための「爆速・軽量・メンテしやすい」コンテナ構成の最適解を叩き込む。
1. マルチステージビルドは「常識」だろ
Dockerファイルをただのスクリプトだと思ってるなら、今すぐ認識を改めろ。ビルドツールや巨大なキャッシュを本番環境まで引きずるのは、泥棒に鍵を渡して家を建てるのと同じだ。
【正解:マルチステージビルド】
# ステージ1: ビルド環境(依存関係の解決とコンパイル)
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
# 開発用依存関係も一時的にインストール
RUN npm ci
COPY . .
RUN npm run build
# ステージ2: 実行環境(最小限のランタイムのみ)
FROM node:18-alpine AS runner
WORKDIR /app
# 実行に必要な依存関係だけを本番用にインストール
COPY package*.json ./
RUN npm ci --only=production
# ビルド済みの成果物のみをコピー
COPY --from=builder /app/dist ./dist
# 【警告】node_modulesを丸ごとコピーするのは危険だ
# ネイティブアドオン(node-gyp等)を利用している場合、
# ビルド環境と実行環境でOSやライブラリのリンク先が異なると実行時に即クラッシュする。
# 必ず「production用に再度インストール」するのがプロの流儀だ。
CMD ["node", "dist/main.js"]
2. 環境汚染を許すな:分離の原則
`docker-compose.yml`を一つにまとめて環境変数で分岐させてるコードを見ると吐き気がする。本番設定と開発設定のコンフリクトは、深夜のデバッグの最大の原因だ。
docker-compose.yml (本番/CIの共通基盤)
services:
app:
build:
context: .
target: runner # 実行用ステージのみを指定
restart: always
docker-compose.override.yml (ローカル開発用)
services:
app:
volumes:
- .:/app # ソースコードをマウントしてホットリロードを実現
command: npm run dev
ports:
- "3000:3000"
environment:
- NODE_ENV=development
この「分離の原則」を守れば、本番環境の安全を担保しつつ、ローカルでは自由な開発体験を享受できる。これがチーム開発における「オンボーディングコスト」を劇的に下げる鍵だ。新人が入社した瞬間に`docker-compose up`で即座に開発開始できる環境。これこそが、技術負債を生まない組織の正体だ。
3. キャッシュの罠:ビルド時間は「順序」で決まる
昔の俺は情弱だった。`COPY . .`を一番上に書いて、コードを1行変えるたびに`npm install`からやり直す地獄にいたんだ。
# 悪い例:変更頻度の高いソースコードを先にコピーすると、
# それ以降のレイヤーキャッシュがすべて無効化される
COPY . .
RUN npm install
【鉄則】変更頻度の低いものから先にコピーしろ
# 1. 依存関係の定義だけ先にコピー
COPY package.json package-lock.json ./
# 2. 依存関係をインストール(package.jsonが変わらない限りキャッシュが効く)
RUN npm ci
# 3. 最後にソースコードをコピー
COPY . .
この順序を守るだけで、CI上のビルド時間は数分から数秒へ激変する。
4. ビルドコンテキストを「断捨離」せよ
`.dockerignore`を忘れるやつは、ビルドが遅いと嘆く資格すらない。ビルドコンテキスト(Dockerデーモンに送られるファイル群)が肥大化すると、ネットワーク転送量が増え、CIのキャッシュ効率が極端に落ちる。
# .dockerignore
.git
node_modules
dist
.env
*.log
coverage
`node_modules`をDockerデーモンに送るなんて、重い荷物を背負ってフルマラソンを走るようなものだ。不要なファイルは徹底的に排除しろ。
なぜ、ここまでやるのか
結局のところ、Dockerの最適化は「どれだけ不要なものを捨てるか」という引き算の美学だ。
「とりあえず動けばいい」という考えは、チームの生産性を食いつぶす癌になる。壊れにくく、誰が触っても速い環境を作ることは、単なる自己満足じゃない。それは「自分たちが開発に集中するための、最強の武器作り」なんだよ。
属人化を排除し、誰もが同じ速度感で開発できる環境を維持することこそ、エンジニアとしての本当の腕の見せ所だ。
結論:イメージは削れ、レイヤーは守れ。それが爆速開発の最低条件だ。