近期要把公司的新官网项目给收尾了,准备打包部署发布到线上环境,我们主要采用的 CircleCI 和 K8S 负责 CICD,就是期间经常会遇到 K8S 的超时错误导致构建失败。
虽然不清楚具体的错误原因,但我发现构建过程中 Dockerfile 生产出来的镜像文件实在是太大了,达到了惊人的 1G 多,想着这样传输镜像的时间肯定会慢,是否因此导致构建失败的概率提升呢?前文详见日记《继续准备 NextJS 新官网项目(二)》
修改配置
想着之前自己部署 NuxtJS 的时候发现它在生产环境下最终运行的是一个 server.mjs 文件,这意味着我或许并不需要安装一大堆 node_modules 依赖,然后再执行 pnpm build && pnpm start
的方式来启动服务。这些最终构建好的代码,小到不足 50MB。
那么 NextJS 可以吗,简单搜索看了下,它是可以做到的。我是从它们官方提供的 Dockerfile 里面找到的这个设置项 output,比较隐蔽。
Next.js can automatically create a standalone folder that copies only the necessary files for a production >deployment including select files in node_modules.
To leverage this automatic copying you can enable it in your next.config.js:
module.exports = {
output: "standalone",
}
修改成这种模式后,意味着项目生产环境的启动方式不再是 pnpm start了,继续这样操作的时候 NextJS 的命令行工具也会对此进行提示。
旧版 Dockerfile
那么在此之前我是怎么做的呢,这是项目之前的 Dockerfile,可以看到构建、运行应用的过程均在里面完成,也因此导致最后的镜像略大。
FROM node:20.15-alpine AS runner
# 定义一个名为 ENV 的参数,默认值为 dev
ARG BUILD_ENV=prod
# Create app directory
WORKDIR /app
RUN addgroup --system --gid 941 nodejs
RUN adduser --system --uid 941 nextjs
COPY . ./
WORKDIR ./
# 如果 BUILD_ENV 为 dev,则复制 .env.dev 到 .env.local
RUN if [ "$BUILD_ENV" = "dev" ]; then cp .env.dev .env.local; fi
RUN chmod 0777 .
RUN npx --yes pnpm install
RUN npx pnpm build
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["npm", "start"]
调整后的 Dockerfile
考虑到我司已经在使用 CircleCI 负责构建应用,K8S 只负责打包并运行构建结果即可,我根据官方的### Dockerfile 最终整理出了一份自己的,供各位参考
FROM node:20.15-alpine AS runner
ENV NODE_ENV production
# Create app directory
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY public /app/public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --chown=nextjs:nodejs .next/standalone ./
COPY --chown=nextjs:nodejs .next/static .next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["ls", "-l"]
# 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
这份 Dockerfile 相较于前面的版本,他多出了一个复制 static(位于项目内 .next/static
) 和 public
(位于项目内/public)文件的步骤,官方描述说是这些文件应由 CDN 处理,但实际情况我们用的 CDN 属于融合 CDN(不知道是不是这么说,类似 CloudFlare 那种自动缓存和回源的),因此不需要额外处理单独托管的静态文件。
因为没有在 Dockerfile 里面安装依赖和构建应用了,因此需要当前的系统环境下,已经通过 pnpm build
完成了 NextJS 的构建过程。
使用 Jenkins 或其他方式
我自己的服务器并没有强大的资源和性能,只有一个机器跑多个服务的使用场景。如果改用传统 Jenkins + SSH + PM2 的部署方式,也是一样轻松了不少,以往需要在运行机器上执行极其缓慢的 pnpm build 也将提前在 Jenkins 机器上完成。通过 SCP 的方式传输构建产物,到运行机器上只需替换掉对应的资源,重启 PM2 就能完成,这里就不再具体提供实现过程了,有需要建议自行尝试摸索。
PS:原文:https://paugram.com/tech/docker-nextjs-deployment-image-too-large.html/comment-page-1#comment-1287