第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) 🧪ミニ演習:サイズと速度が落ちるのを体感しよう 📉⏱️
- いまのDockerfileでビルド
- この章のDockerfile(マルチステージ+omit)に置き換えてビルド
- 変化を確認👇
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ビルド自体をさらに速くする(キャッシュしやすい粒度、成果物コピーの最適化)に突っ込みます 😆⚡️