Skip to main content

第19章:開発依存と本番依存を切り分ける ✂️📦⚡️

この章のテーマはシンプル👇 **「本番コンテナに、開発ツールを持ち込まない」**です 😎✨

そうすると…

  • 📦 イメージが軽くなる(転送も展開も速い)
  • ⚡️ 依存インストールが速くなる(入れる数が減る)
  • 🛡️ 攻撃面が減る(余計なパッケージを減らせる)

…という「速度・サイズ・安全」ぜんぶに効くお得パックになります 👍


1) まず言葉をゆるっと整理しよう 🧠

dependencies:本番で動かすのに必要なやつ(例:Webサーバ、DBクライアントなど) devDependencies:開発やビルドの時だけ必要なやつ(例:TypeScript、Lint、テスト)

TypeScriptプロジェクトだと、だいたいこんな感じ👇

  • 🧰 devDependencies になりがち typescript, ts-node, eslint, prettier, jest, vitest, @types/* など
  • 🚀 dependencies になりがち express, fastify, zod, pg, mysql2, dotenv など

ポイントはこれ👇 本番は「実行」だけできればOK。型チェックもLintもテストも、実行時には要らない 😊


2) “切り分け”の王道はマルチステージ 🏗️🪶

Dockerfileを ビルド用実行用 に分けるやつです。 最後のイメージに「必要な成果物だけ」持ち込めるのが強い💪 (=開発ツールをごっそり置いていける) (Docker Documentation)

ざっくり図解👇

[builder]  devDeps込みでビルドする 🛠️
└ dist/ を作る

[runner] prodDepsだけ入れて実行する 🚀
└ dist/ だけ持ってくる

3) npm 版:鉄板レシピ(TypeScript→dist実行)🥇

ポイントは2つだけ👇

  • builder:npm ci で全部入れてビルド
  • runner:npm ci --omit=dev で devDependencies を物理的に入れない (npmドキュメント)
## syntax=docker/dockerfile:1

#############
## 1) builder #
#############
FROM node:24-slim AS builder
WORKDIR /app

## 依存は先に(キャッシュが効く)
COPY package.json package-lock.json ./
RUN npm ci

## ソースを入れてビルド
COPY . .
RUN npm run build

##############
## 2) runner #
##############
FROM node:24-slim AS runner
WORKDIR /app

## 本番依存だけ入れる(devDependenciesは入れない)
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

## ビルド成果物だけ持ち込む
COPY --from=builder /app/dist ./dist

## 本番起動
CMD ["node", "dist/index.js"]

✅ これで「本番イメージ」には TypeScript も ESLint も Jest も入りません🎉 (必要なのは dist と runtime deps だけ)

ちなみに公式のNodeイメージは -slim-alpine などのバリアントがあり、軽量化の方向性を選べます。 (Docker Hub)


4) npm の “地味に重要” な挙動:NODE_ENV=production で dev が省かれる話 🧨

npm は、NODE_ENV=production のとき デフォルトで dev を omit する挙動があります。 (npmドキュメント)

これ、便利そうで落とし穴もあって…😵‍💫

  • builder ステージで NODE_ENV=production が入ってると、ビルドに必要な devDeps が入らず失敗しがち

なので教材的にはおすすめは👇 ✅ runner では npm ci --omit=dev を明示 ✅ builder は npm ci で普通に全部入れる(環境変数に頼らない)


5) pnpm 版:公式も「ステージ分割」を推してる 🍱⚡️

pnpm の公式ドキュメントでも、devDependenciesはビルド段階だけにして、最後は pnpm install --prod の別ステージにしよう、という流れが紹介されています。 (pnpm)

さらにモノレポなら pnpm deploy で「必要なものだけ持ち出す」やり方も用意されてます。 (pnpm)

(ここはpnpm派の人だけ覚えればOK🙆)


6) よくある事故(ここ超大事)🚧😭

事故1:本番で必要なものをdevDependenciesに入れてた 例:実行時に読む設定/ライブラリが dev に入ってると、本番で落ちます💥 👉 対策:本番起動して落ちたら「それ、本番依存では?」を疑う

事故2:distを作るのに devDeps が必要なのに、runnerでビルドしてた 👉 対策:ビルドは必ずbuilderでやる(runnerは実行専用)

事故3:node_modules を builder から丸ごとCOPYしてしまう devDepsまで全部運んでしまい、切り分けの意味が消えます😇 👉 対策:runnerで --omit=dev して入れ直す or prune --prod 系で削る


7) 🧪ミニ演習:サイズと速度が落ちるのを体感しよう 📉⏱️

  1. いまのDockerfileでビルド
  2. この章のDockerfile(マルチステージ+omit)に置き換えてビルド
  3. 変化を確認👇
docker image ls
docker history <image-name>

見るポイントはこれ👇

  • 📦 イメージサイズ(MB/GB)
  • 🧱 依存インストールの時間
  • 🧾 history で「node_modulesがでかい層」が減ったか

8) 🤖AI活用(Copilot / GitHub / OpenAI系ツール想定)プロンプト集 🧰✨

プロンプトA:依存の仕分けレビュー

package.json を貼るので、dependencies と devDependencies の仕分けが妥当かレビューして。
「本番で必要なのに dev に入ってる可能性」「開発専用なのに deps に入ってる可能性」を指摘して、理由も添えて。

プロンプトB:Dockerfileの“本番イメージに混入してる物”監査

このDockerfileで、本番イメージに devDependencies やビルドツールが混入する可能性がある箇所を列挙して。
混入を防ぐ改善案を3つ、差分イメージで出して。

プロンプトC:落ちた時の原因当て

本番コンテナで起動するとこのエラーになる:<エラーログ>
devDependencies を omit した構成で起きる原因候補を優先度順に出して。
必要なら確認コマンドも提案して。

9) 仕上げチェックリスト ✅🏁

  • ✅ builder でビルドしてる(runnerでビルドしてない)
  • ✅ runner は npm ci --omit=dev(またはpnpmのprod相当)になってる (npmドキュメント)
  • ✅ runner に dist/ だけ持ち込んでる(node_modules丸ごとコピーしてない)
  • ✅ 「本番で必要な依存」が devDependencies に紛れてない

次の章(第20章)は、この流れのまま TypeScriptビルド自体をさらに速くする(キャッシュしやすい粒度、成果物コピーの最適化)に突っ込みます 😆⚡️