目前项目中有个服务使用的是 Next.js 框架,在生产部署的时候,因为它build 之后不是一个纯静态的东西,并且需要依赖node_modules中的一些依赖,传统的部署方式导致整个包特别大。之前有想过如何将只用到的依赖拿出来,然后打成镜像部署,但是因为自己水平有限,身为后端对这块不太清楚,最终以失败告终。但是现在因为某些原因,决定重新将解决这个问题。
现在要考虑的问题
- 如何将前端项目打成镜像
- 如何将必须的东西打进镜像
- 如何最小化镜像
如何将前端项目打成镜像
这个问题很简单,只需要按照项目正常构建过程执行就行,只不过是在Dockerfile
流程化完成了这一步
查看代码
1 | # 使用官方的Node.js slim镜像作为基础镜像 |
如何将必须的东西打进镜像
如果使用最基础的方式部署Nest.js
应用,会导致镜像体积过大进而影响到镜像构建的效率和大小。
因为node_modules
是必须的,但其中的依赖不是所有都需要,现在需要考虑如何移除node_modules
中的非必要依赖。
通过查阅资料,Next.js|output,发现官方文档中有一栏专门解决这个问题。
output
During a build, Next.js will automatically trace each page and its dependencies to determine all of the files that are needed for deploying a production version of your application.
This feature helps reduce the size of deployments drastically. Previously, when deploying with Docker you would need to have all files from your package’s dependencies installed to run next start. Starting with Next.js 12, you can leverage Output File Tracing in the .next/ directory to only include the necessary files.
Furthermore, this removes the need for the deprecated serverless target which can cause various issues and also creates unnecessary duplication.
翻译如下
在生成过程中,Next.js 将自动跟踪每个页面及其依赖项,以确定部署应用程序的生产版本所需的所有文件。
此功能有助于大幅减小部署规模。以前,使用 Docker 进行部署时,需要 dependencies 安装包中的所有文件才能运行 next start 。从Next.js 12 开始,您可以利用 .next/ 目录中的输出文件跟踪仅包含必要的文件。
此外,这消除了对已 serverless 弃用目标的需求,这可能会导致各种问题,并产生不必要的重复。
具体原理详见https://nextjs.org/docs/pages/api-reference/next-config-js/output
总结来说:
Next.js@12
提供了一个 standalone
模式,通过在 next.config.js
中设置该选项,在执行 next build
指令后可以自动创建一个独立文件夹,只复制生产部署所需的必要文件,大幅减少应用体积。
首先修改next.config.js
配置:
1 | const nextConfig = { |
在执行 npm build
之后,会生成如下结构的目录
这将创建一个文件夹 .next/standalone
,然后可以在该文件夹中自行部署,而无需安装 node_modules
。
此外,还输出了一个最小的 server.js
文件,可以使用它来代替 next start
.默认情况下,此最小服务器不会复制 public
or .next/static
文件夹,因为理想情况下,这些文件夹应由 CDN 处理,尽管这些文件夹可以手动复制到 standalone/public
and standalone/.next/static
文件夹,之后 server.js
文件将自动提供这些文件夹。
这个过程其实就可以大大缩减镜像的大小了,因为去除了无必须要的node_modules 依赖。
如何最小化镜像
最后,如何将镜像最小化构建是我们的最终目标。
遵循 Dockerfile
的最佳实践
- 尽量使用官方的基础镜像,Docker推荐使用Alpine的镜像。
- 使用多阶段构建
- 使用.dockerignore去除无关的文件
- 创建临时容器
- 不要安装不用的包
- 解耦应用程序
- 利用缓存构建镜像
- 拆分复杂的RUN命令为多行,并用 / 分割
更多详细的最佳实践可以参考官方文档:https://docs.docker.com/develop/develop-images/instructions/
我们这次只考虑 使用多阶段构建 后续再专门研究
编写的Dockerfile
如下(不是最终版)
查看代码
1 | # 设置基础依赖层,使用Node 16并安装必要兼容库 |
如上最终将部署的650M
的服务(有点扯蛋),改成了150M
左右,结果还是不错的,但是任重而道远,还有许多地方要优化,一步一步来吧。
- 如何进一步减小镜像大小
- 如何缩短构建的时间
Next.js
官方 Dockerfile
样例
https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile
查看代码
1 | FROM node:18-alpine AS base |
参考文章