Node.js on Dockerでスリムかつセキュアなコンテナイメージを作る - 2025年版
はじめに
みなさんはDockerfileを書いていますか?
近年はAIによる支援でDockerfileをスクラッチで書く機会が減っていると考えられます。
しかしながら、AIに書かせる場合でも良い書き方を知っていることで、その後の修正が容易になります。
この記事では、Node.jsアプリケーションのDockerfileを作成する際に、私ならこのように書くという提案をします。
作成するDockerfileとディレクトリ構造
初めに、完成形のDockerfileとディレクトリ構造を示します。
以降のセクションで実装のポイントを解説していきます。
実行可能なサンプルコードはGitHubで公開しているので、必要に応じてこちらも参照してください。
Dockerfile
各種バージョンは記事執筆時点での最新版です。
# syntax=docker/dockerfile:1.20.0
FROM node:24.11.1-bookworm-slim AS builder
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable pnpm
WORKDIR /app
COPY pnpm-lock.yaml /app
RUN pnpm fetch
COPY . /app
RUN pnpm install --offline
RUN pnpm run build
RUN pnpm prune --prod --ignore-scripts
FROM gcr.io/distroless/nodejs24-debian12:nonroot AS app
ENV NODE_ENV=production
WORKDIR /app
COPY /app/dist /app/dist
COPY /app/node_modules /app/node_modules
COPY /app/package.json /app/package.json
ENV NODE_OPTIONS="--enable-source-maps"
CMD ["dist/index.js"]
ディレクトリ構造
.
├── Dockerfile
├── LICENSE
├── package.json
├── pnpm-lock.yaml
├── src
│ └── index.ts
└── tsconfig.json
Dockerfile全行解説
# syntax=docker/dockerfile:1.20.0
Dockerfileの構文を指定します。
バージョンの固定にこだわりがなければdocker/dockerfile:1にします。
FROM node:24.11.1-bookworm-slim AS builder
Node.jsイメージのバージョン指定は24.11.1-bookworm-slimにしていますが、お好みでハッシュ値にします。
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
pnpmが今回の構成で中心的役割を果たします。
RUN corepack enable pnpm
corepackを使ってpnpmを有効化します。
package.jsonのpackageManagerフィールドでpnpmのバージョンを指定しておきましょう。
WORKDIR /app
COPY pnpm-lock.yaml /app
RUN pnpm fetch
先行してpnpm-lock.yamlをコピーしてから、pnpm fetchの準備をします。
pnpm fetchはpnpm-lock.yamlの内容に基づいて仮想ストアにパッケージをダウンロードします。
package.jsonに依存しないため、package.jsonに変更がない場合にキャッシュが効きやすくなります。
公式ドキュメントでも言及されているように、Dockerイメージの作成に役立つコマンドです。
--mount=type=cache,id=pnpm,target=/pnpm/storeを指定することで、実行環境によってはビルド毎にキャッシュを再利用可能です。
COPY . /app
RUN pnpm install --offline
pnpm fetchで先行してダウンロードした依存パッケージをインストールします。
--offlineオプションを付与することで、ネットワークアクセスを行わず高速に実行可能です。
RUN pnpm run build
RUN pnpm prune --prod --ignore-scripts
ビルドステージのnode_modulesは実行環境にコピーするのでpnpm prune --prodでdevDependenciesを削除します。
FROM gcr.io/distroless/nodejs24-debian12:nonroot AS app
実行環境にはDistrolessイメージを使用します。
Distrolessはnonrootイメージが提供されており、利用可能な場面では積極的に採用します。
ENV NODE_ENV=production
WORKDIR /app
COPY /app/dist /app/dist
COPY /app/node_modules /app/node_modules
COPY /app/package.json /app/package.json
ビルドステージからアプリケーションの実行に必要なファイルのみをコピーします。
--chown=nonroot:nonrootを指定して、ファイルの所有者をnonrootユーザーに変更します。
ENV NODE_OPTIONS="--enable-source-maps"
CMD ["dist/index.js"]
--enable-source-mapsオプションを指定して、スタックトレースにソースコードの情報を含めます。
CMDはpackage.jsonのmainフィールドを記載している場合は["."]でカレントディレクトリのみを指定可能です。
おわりに
この記事はpnpm公式のWorking with Dockerを参考にDockerfileを作成したところ、細かい改善ポイントが見つかったため、改修を加えた内容になります。
pnpmは機能追加が頻繁に行われているためリリースを継続的にウォッチすることをお勧めします。
この記事はNode.js、TypeScript、pnpmを前提とした内容になりますが、他のパッケージマネージャーやランタイムでも取り入れられる考え方や手法があるので参考にしてみてください。
Discussion
pnpm v10以降を使用しているならcorepackは追加しなくていい...はずです。