y-ohgi's blog

TODO: ここになにかかく

Next.js v14用マルチステージビルドDockerfile

TL;DR

  • 思ったより綺麗なNext.js v14用Dockerfileが書けた記念
  • distroless/nodejs22を使用。530MBのイメージ。

本題

Dockerfile本体

# === base
FROM --platform=${BUILDPLATFORM:-linux/arm64} node:22-slim AS base

# === deps
FROM base AS deps

WORKDIR /app

COPY package* .

RUN npm install

# === builder
FROM base AS builder

WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules

COPY . .

RUN npm run build

# === prod deps
FROM base AS prod_deps

WORKDIR /app

COPY --from=deps /app/package* .
COPY --from=deps /app/node_modules ./node_modules

RUN npm ci --omit=dev

# === runner
FROM --platform=${BUILDPLATFORM:-linux/arm64} gcr.io/distroless/nodejs22-debian12 AS runner

WORKDIR /app

ENV HOSTNAME=0.0.0.0
ENV NODE_ENV=production
ENV PORT=3000

COPY --from=prod_deps --chown=nonroot:nonroot /app/package* .
COPY --from=prod_deps --chown=nonroot:nonroot /app/node_modules ./node_modules
COPY --from=builder --chown=nonroot:nonroot /app/public ./public
COPY --from=builder --chown=nonroot:nonroot /app/.next ./.next

USER nonroot

EXPOSE 3000

CMD ["node_modules/.bin/next", "start"]

解説

# === base
# マルチプラットフォーム対応のベースとなる中間イメージ。ベースイメージとしてこれを使用する。
FROM --platform=${BUILDPLATFORM:-linux/arm64} node:22-slim AS base

# === deps
# npm install をするだけの中間イメージ
# composeでtargetをこのイメージにすることで使用することを想定。ローカル開発用に分けている。
FROM base AS deps

WORKDIR /app

COPY package* .

RUN npm install

# === builder
# nextのビルドを実行し、 ".next" ディレクトリを生成
FROM base AS builder

WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules

COPY . .

RUN npm run build

# === prod deps
# 最終イメージで使用しないパッケージをオミットする中間イメージ
FROM base AS prod_deps

WORKDIR /app

COPY --from=deps /app/package* .
COPY --from=deps /app/node_modules ./node_modules

RUN npm ci --omit=dev

# === runner
# distrolessを使用した最終イメージ
FROM --platform=${BUILDPLATFORM:-linux/arm64} gcr.io/distroless/nodejs22-debian12 AS runner

WORKDIR /app

ENV HOSTNAME=0.0.0.0
ENV NODE_ENV=production
ENV PORT=3000

# 必要最低限なnode_modulesを取得
COPY --from=prod_deps --chown=nonroot:nonroot /app/package* .
COPY --from=prod_deps --chown=nonroot:nonroot /app/node_modules ./node_modules
# 生成したディレクトリを取得
COPY --from=builder --chown=nonroot:nonroot /app/public ./public
COPY --from=builder --chown=nonroot:nonroot /app/.next ./.next

# nonrootにすることでセキュリティ対策
USER nonroot

EXPOSE 3000

# distrolessはnpmが入っていないので、nextコマンドから直接起動
CMD ["node_modules/.bin/next", "start"]