Dockerのマルチステージビルド デプロイを壊さずにイメージを小さくする
マルチステージDockerビルドの実践的な解説 — 実際の前後画像サイズ、Node.jsおよびPython用のDockerfileの例、そしてすべてのチームが少なくとも一度は直面する課題。
あなたの Node.js イメージは 1.1 GB です。以下を追加しました .dockerignore、開発依存関係を削除し、試行しました node:slim — ほとんど変化がありませんでした。実際の解決策はマルチステージビルドです。まだ切り替えしていない場合は、TypeScript コンパイラを生産環境に送信しています。
マルチステージビルドは、Docker 17.05(2017年)以降から存在しています。それらはあまり使われていません。ここに実際のステップバイステップの解説を示します:どのような変更が生じるか、差分がどれだけ大きいか、そしてチームが初めて移行時に遭遇する3つの危険なポイント。
シングルステージの問題
ほとんどの Dockerfile はこう始まります:
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/server.js"]
を構築し、を確認します docker images: ~1.1 GB。あなたは完全な Node 20 イメージ、npm、TypeScript ツールチェーン、すべての開発依存関係、および完全なソースツリーを送信しています。それらは生産環境で実行されません — アプリはただコンパイルされた dist/ 出力と少数のランタイムパッケージだけが必要です。
マルチステージビルド:解決策
すべての FROM インストラクションは、クリーンなファイルシステムを持つ新しいステージを開始します。ステージを名前付けて AS、その後、 COPY --from=stagename を使用して、次のステージに特定のファイルを引き渡します。中間ステージは最終イメージに含まれず、ビルドが終了した後、廃棄されます。 COPY が終了した後です。
以下は、適切なマルチステージビルドのアプリです:
# ---- Build stage ----
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ---- Runtime stage ----
FROM node:20-alpine AS runtime
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]
重要な行: COPY --from=builder /app/dist ./dist。これは、ビルダーステージからコンパイルされた出力をアライアンスベースのランタイムイメージに引き渡します。TypeScript コンパイラ、ソースファイル、および開発依存関係は最終レイヤーに一切触れないです。 だけに分割します。 結果:
~160 MB ではなく、1.1 GB です。これは、典型的な Node アプリに対して約 85% の削減を意味し、同じビルドアーティファクトを、その周囲の構成を除いて提供しています。 テストステージの追加
ビルドとランタイムの間にテストステージを追加して、テストセットを実行できます。テストが失敗した場合、ランタイムイメージの作成が停止します。テストが通過した場合、生産環境でのビルドではテストステージを完全にスキップします。
CI では、テストステージを明示的にターゲットにします:
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM builder AS tester
RUN npm test
FROM node:20-alpine AS runtime
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]
。生産イメージではターゲットを指定せず、Docker はすべてのステージを順番に実行し、最後の docker build --target tester .まで実行します。テストステージは実行されますが、ファイルシステムは廃棄されます — テストはゲートとして機能し、パッケージとしての負荷ではありません。 FROMPython:同じアイデア、わずかな実行の違い
Python のマルチステージビルドは同じパターンを追います。主な違いは、pip が
を使用すると、パッケージを /root/.local の下にインストールするため、そのディレクトリをスリムランタイムイメージにコピーするということです。 --userベースイメージ:~1 GB。
FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
COPY . .
FROM python:3.12-slim AS runtime
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY --from=builder /app .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "main.py"]
python:3.12 のみインストールされた依存関係:~180–250 MB(requirements.txt に含まれる内容によって異なります)。コンパイルされた python:3.12-slim ファイルは、ソースファイルの隣に存在するため、無料で提供されます。 .pyc 3つの危険なポイントが、すべての人が少なくとも一度遭遇します
1. 間違ったファイルをコピーする
最も一般的なミス:あなたは
。あなたはすべてのものをコピーしました — ソースファイル、テストフィクスチャ、node_modules、その他 — あなたの「最小」ランタイムイメージに。それにより、シングルステージバージョンよりも大きくなっています。 COPY --from=builder /app ./ の代わりに COPY --from=builder /app/dist ./distコピーするべきファイルを明確にします。実際のエントリポイントが必要とするディレクトリまたはファイルだけをコピーします。Node アプリの多くでは:コンパイルされた出力(
)と、静的資産をオプションとして。Python では:インストールされたパッケージとアプリケーションコード、requirements.txt、テスト、ノートブックを除く。dist/2. ビルド引数にシークレットが層に漏れる
もし、シークレットをビルド引数として渡す(例:
を追加し、 ARG NPM_TOKEN コマンドで使用する)場合、そのシークレットはすべての次の層に表示されます — すなわちマルチステージビルドでも同じです。 RUN が表示します。 docker history myimage 正しいアプローチは、Docker BuildKit の
です。シークレットはその層の実行時にのみマウントされ、イメージの履歴に決してコミットされません。次のようにビルドします: --mount=type=secret:
RUN --mount=type=secret,id=npm_token NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci
シークレットを同じ docker build --secret id=npm_token,src=.npmrc .
層で削除するという、簡単な代替策は、マルチステージビルドでは実際に効果がありませんが、BuildKit アプローチは常によりクリーンです。 RUN 3. .dockerignore を忘れる
マルチステージビルドは最終イメージを小さくしますが、
ビルドステージでは、Docker ダイアログにすべてのコンテキストを送信します。.dockerignore がない場合、それは COPY . . 、テストフィクスチャ、ローカル .dockerignoreファイル、および明示的にファイルに保存されているシークレットを含みます。ビルドステージはすべてを認識します。 .git/, node_modules/最小 .env ノードプロジェクトのためのもの:
. .dockerignore を追加したその日から。ビルドコンテキストサイズは、 .dockerignore 出力の最初の行(
.git
node_modules
dist
.env
*.log
coverage
.nyc_output
追加する .dockerignore )に表示されます — その数字が異常に大きい場合、含まれているものを確認します。 DockerfileDockerfile 作業に役立つツール docker build もしくは、独自に書く前に、IO Tools でマルチステージ Dockerfile をスケルトン化します。何かを書いた後、それをSending build context to Docker daemon X MBを通じて、よくあるミスを事前に検出します — 例えば、欠落した
、
タグの使用、またはrootとして実行していること。 Dockerfile 生成ツール 要点 Dockerfile リンター & フォーマッター マルチステージビルドは2ステップの変更です:名前付きのビルドステージを追加し、コンパイルされた出力を新しい最小イメージにコピーします。サイズの削減はほぼ常に価値があります — Node および Python アプリでは80–90%が一般的です。主な課題は、 WORKDIRをあまり広く設定すること、ビルド引数にシークレットを漏らすこと、および latest をスキップすることです。これらを修正すれば、実際に生産環境に適したサイズのイメージが得られます。
要点
マルチステージビルドは、2つのステップで変更されます:named build stageを追加し、コンパイルされた出力を新しい最小イメージにコピーします。サイズの削減はほぼ常に価値があります——NodeおよびPythonアプリケーションでは80~90%が一般的です。主な課題は、設定の範囲を広く設定しすぎること、ビルド引数にシークレットを漏らすこと、およびをスキップすることです。 COPY --from、ビルド引数にシークレットを漏らすこと、およびをスキップすることです。 .dockerignore。それらを修正すれば、実際にプロダクション向けにサイズが適切なプロダクションイメージが完成します。
恵 スコアボードが到着しました!
スコアボード ゲームを追跡する楽しい方法です。すべてのデータはブラウザに保存されます。さらに多くの機能がまもなく登場します!
