Ray Lee | 李宗叡
Learn or Die
Published in
7 min readAug 22, 2024

--

# 前言

紀錄將 Next.js 專案 build 成 Docker image 的過程

# Dockerfile

FROM node:20-alpine AS base

# 若專案有特別定義 Yarn version,則這邊要特別指定。 node:20 的預設 Yarn version 為 1.x.x
RUN yarn set version 4.3.1

# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Install dependencies based on the preferred package manager
# .yarnrc.yml 要是沒有複製,yarn --frozen-lockfile 執行完畢之後,不會產生 node_modules
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .yarnrc.yml ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi


# 1. 將 deps 階段產生的 node_modules 複製過來
# 2. 將專案內容複製過來
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Next.js 預設會收集使用的一些資料,可以透過 NEXT_TELEMETRY_DISABLED 參數關閉分享
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.

ENV NEXT_TELEMETRY_DISABLED=1

# 3. build 產生 production 必要檔案
RUN \
if [ -f yarn.lock ]; then yarn run build; \
elif [ -f package-lock.json ]; then npm run build; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
else echo "Lockfile not found." && exit 1; \
fi

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

# Uncomment the following line in case you want to disable telemetry during runtime.
ENV NEXT_TELEMETRY_DISABLED=1

# 1. 這邊定義了特定的 user & group,避免使用 root 來運行 process,這是安全性的最佳實踐,這樣儘管 process 被入侵,入侵者也只能擁有 nextjs:nodejs 的權限
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# 2. 將 build 階段產生的 public dir 複製過來
COPY --from=builder --chown=nextjs:nodejs /app/public ./public

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next

# 3. 如果有在 next.config.js file 中指定 {output: 'standalone'} 的話,那在 build 時會產生一個 standalone dir,裡頭會有可以運行服務的最小資料,不需要再執行 next start
# 4. 將 standalone 裡頭的資料都複製過來,並且將 build 產生的 .next/static 裡頭的靜態檔案也複製過來。如果有使用 CDN 的話,這個步驟可以省略
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

# 5. 定義 process 的 user
USER nextjs

# 6. 開放 container port 號
EXPOSE 3000

# 7. 指定 port,如果在 .env 當中有指定,那這邊則不需特別指定
ENV PORT=3000

# 8. server.js 唯有當使用 standalone config 時,build 時才會產生,他可以用來取代 next start
# 9. 使用 standalone 的目的在於盡可能的縮小 image size
# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD HOSTNAME="0.0.0.0" node server.js

# standalone config

  • next.config.mjs 檔案中,定義 {output: 'standalone'}
  • 當使用 standalone config 時,會在 yarn build 時產生一個名為 standalone 的 Dir,裡頭會有 production 會使用到的「最少量」資料
  • 目的在於 build image 時,可以讓 image 只含有「最少量」的 production 所需資料,減少 image size
  • 細節可參考 官方文件

# build

最後執行 docker build . -t imageName -f yourDockerfileLocation

# 參考資料

--

--

Ray Lee | 李宗叡
Learn or Die

It's Ray. I do both backend and frontend, but more focus on backend. I like coding, and would like to see the whole picture of a product.