17
8

Next.jsアプリケーションのイメージサイズを劇的に削減するマルチステージビルドの魔法

Posted at

イメージサイズ削減の重要性

Next.jsアプリケーションを開発する際、コントロールを怠るとイメージサイズの増加が致命的なパフォーマンス低下やデプロイメントの障害に直結します。
特に、クラウドプラットフォームを使用している場合、イメージサイズが肥大化すると、デプロイメントやスケーリングの速度が著しく低下し、予想外のコスト増加を招く可能性が高まります。
この記事では、Next.jsアプリケーションのイメージサイズを効果的に削減し、これらのリスクを回避するためのマルチステージビルド手法に焦点を当てます。
Dockerを駆使してビルドプロセスを最適化し、不要なファイルや依存関係を徹底的に排除することで、イメージサイズを大幅に削減し、クラウド環境でのアプリケーションのデプロイメントやパフォーマンスを大きく向上させることが可能です。

結果

1.91GBから162MBになりました。

REPOSITORY SIZE
single-stage-build 1.91GB
multi-stage-build 162MB

変更前

FROM node:18-alpine

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install

COPY . .

RUN npx prisma generate
RUN npm run build

EXPOSE 3000

CMD ["npm", "start"]

公式サンプル

こちらを参考にしました。
https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile

standaloneモード

standaloneとは?
https://nextjs.org/docs/app/api-reference/next-config-js/output#automatically-copying-traced-files

Next.jsが自動的に必要なファイルだけをコピーするスタンドアロンのフォルダを作成できます。
これにより、node_modulesのインストールなしでデプロイできるようになります。さらに、最小限のserver.jsファイルも出力され、これはnext startの代わりに使用できます。
ただし、この最小限のサーバーはデフォルトではpublicフォルダや.next/staticフォルダをコピーしないため、これらのフォルダは手動でstandalone/publicやstandalone/.next/staticフォルダにコピーする必要があります。その後、server.jsファイルはこれらを自動的に提供します。

// next.config.js
module.exports = {
  // ... rest of the configuration.
  output: "standalone",
};

変更後

# base ステージ
FROM node:18-alpine AS base

# deps ステージ
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package*.json ./
RUN npm ci

# builder ステージ
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

RUN npx prisma generate
RUN npm run build

# runner ステージ
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

RUN mkdir .next
RUN chown nextjs:nodejs .next

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000

CMD HOSTNAME="0.0.0.0" node server.js

各ステージの説明と目的

base ステージ

  • 目的: 全てのステージで共通の基盤となるイメージを提供します
  • 役割: node:18-alpineをベースイメージとして使用し、これにより他のステージでNode.js環境が利用可能になります

deps ステージ

  • 目的: アプリケーションの依存関係をインストールします
  • 役割: libc6-compatパッケージをインストールし、package.jsonとpackage-lock.jsonをコピーしてから、npm ciコマンドを実行して依存関係をインストールします。これにより、後続のステージで依存関係を再利用できます。libc6-compatパッケージのインストールは、Alpine Linux上でglibcに依存するバイナリが正しく動作するようにするために必要です。これは、特定のNode.jsパッケージや依存関係がglibcに依存している場合に重要となります。

builder ステージ

  • 目的: アプリケーションのビルドを行います。
  • 役割: depsステージでインストールされたnode_modulesをコピーし、アプリケーションのソースコードをコピーします。その後、Prismaクライアントを生成し(npx prisma generate)、アプリケーションをビルドします(npm run build)。このステージでは、ビルドプロセスに必要な全ての操作が行われます。

runner ステージ

  • 目的: ビルドされたアプリケーションを実行環境に準備します
  • 役割: 専用のユーザー(nextjs)とグループ(nodejs)を作成し、builderステージでビルドされたアプリケーションのファイルを適切な場所にコピーします。最後に、nextjsユーザーでアプリケーションを実行します。
    このマルチステージビルドプロセスにより、ビルド時と実行時の依存関係を明確に分離し、最終的なイメージのサイズを小さく保つことができます。また、セキュリティリスクを最小限に抑えることができます。

まとめ

信じられないことに、イメージのサイズが1.91GBからわずか162MBにまで削減されました!
これは、なんとサイズが10分の1以下になったことを意味します!
1.91GBは約1,910MBですが、それがたったの162MBにまで削減されたのですから、なんと約11.8倍ものサイズ削減が達成されたことになります!
まさに驚異的な最適化が達成され、サイズが驚くほど大幅に削減されたことが証明されました!

17
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
8