NestJS小技巧04-NestJS在Docker中的打包和发布

1,418 阅读4分钟
by 雪隐 from https://juejin.cn/user/1433418895994094
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权

大家好我是雪隐,请叫我雪宝,欢迎来到NodeJS框架NestJS的第四个提示和技巧系列。我会介绍下用NestJS用Docker发布时我遇到的一些问题。

优化前的打包方式

在我不懂事的时候,我一般会把发布的DockerfileDocker-compose写成下面这个样子,有没有小伙伴们和我一样的。

Dockfile文件

FROM node:latest
# 创建目录
RUN mkdir -p /home/app/
# 指定工作目录
WORKDIR /home/app/
# 复制package.json文件
COPY ./dist/package.json ./
# 安装依赖
RUN npm install
# 复制编译好的项目文件
COPY ./dist .
# 复制配置文件
COPY ./.config ./.config/
# 创建log的目录卷
VOLUME [“/logs”]
# 暴露8000端口
EXPOSE 8000
# 程序启动命令
CMD ["node", "src/main.js"]

Docker-compose文件

version: "3"
services:
  gateway-service-dev:
    container_name: gateway-service-dev
    build:
      # 编译并且关联镜像
      context: ./
      dockerfile: Dockerfile
    ports:
      # 端口
      - "8000:8000"
    volumes:
      # 挂载日志
      - ./nestjs/logs:/home/app/logs
    environment:
      # 环境变量
      RUNNING_ENV: 'prod'

如果只是自己玩玩还好,如果这是要打包上线的,而且发布的人不是自己,那么很有可能会被发布的人鄙视或者投来异样的眼光,尤其是如果项目的功能很小,你会发现,这样发布Nest至少要占用1.5G左右甚至更多的磁盘空间。

这里面有2个比较有问题的点,一个是Dockerfile的FROM node:latest,它的镜像大小估计在1G左右,第二个是RUN npm install即使您的Nest项目什么也不做,估计也会有200~300M的依赖会被下载进来,而且比较费时。 还有一个小问题是,根本不需要Dockerfile这个文件。因为Docker-compose完全可以一手操办所有的事情。

问题解析

  1. FROM node:latest

首先不建议大家直接写latest,因为环境的升级可能会导致某些功能无法使用。最好固定一个版本。之后再讨论优化。在讨论优化之前,先请大家看下这篇文章。这文章的写了很详细Node关于Docker的最佳实践。 How to Use the Node Docker Official Image

不嫌麻烦的朋友的还是建议完整看一遍原文。我这里简单的描述下重点内容。 镜像node:大版本或者node:大版本-bullseye的包里面含有大量开发工具包。我们拿node:19-bullseye的版本来举例,如果要发布的话建议我们给环境瘦身。通过下面的命令可以删除开发依赖包(devDependencies)。

FROM node:19-bullseye
RUN npm prune --production

甚至我们可以直接利用已经瘦身好的环境node:19-bollsesye-slim。它直接比总包少了75%的体积。 更厉害的情况是可以用最小包node:19-alpine,但是官方没有对他进行维护(如果是很懂的朋友可以直接用这个包,包里面很多基础的工具都没有,比如curl等等,所以安装这个包的时候最好手动装一些自己需要的基础工具)。

其他还有很多建议,比如不要放在根目录,设置用户权限和环境变量,限制内存上限等等。

  1. RUN npm install

这个命令相信大家都很熟悉,但是大家要清楚,NestJS基本都是用TypeScript开发的,有很多的第三方type文件,还有启动的工具包 webpack等等。直接npm install是很不负责任的。这些只在开发中起作用的包也会被一并安装到正式环境中。

// package.json
  "dependencies": {
    "@nestjs/common": "^9.0.0",
    "@nestjs/core": "^9.0.0",
    "@nestjs/mapped-types": "*",
    "@nestjs/platform-express": "^9.0.0",
    "reflect-metadata": "^0.1.13",
    "rxjs": "^7.2.0"
  },
  // 正式环境中用不到的包。
  "devDependencies": {
    "@nestjs/cli": "^9.0.0",
    "@nestjs/schematics": "^9.0.0",
    "@nestjs/testing": "^9.0.0",
    "@types/express": "^4.17.13",
    "@types/jest": "29.2.4",
    "@types/node": "18.11.18",
    "@types/supertest": "^2.0.11",
    "@typescript-eslint/eslint-plugin": "^5.0.0",
    "@typescript-eslint/parser": "^5.0.0",
    "eslint": "^8.0.1",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-prettier": "^4.0.0",
    "jest": "29.3.1",
    "prettier": "^2.3.2",
    "source-map-support": "^0.5.20",
    "supertest": "^6.1.3",
    "ts-jest": "29.0.3",
    "ts-loader": "^9.2.3",
    "ts-node": "^10.0.0",
    "tsconfig-paths": "4.1.1",
    "typescript": "^4.7.4"
  },

正确的打包方式。

RUN npm install --production

另外,NestJS是利用Webpack来打包,但是Nest官方的Webpack默认基础配置文件把一些包排除了,我们甚至可以自己写webpack.config.js,把所有线上要用到的依赖都打包在一起。从而可以省略掉npm install这一步。有兴趣的小伙伴们可以研究下。

  1. 整合成一个

最后,我把最初的2个文件整合成立了一个,只是一个简单的例子,上线版本权限什么的大家自己去考虑。请记住上传node_modules到服务器的时候最好压缩成zip文件,然后在服务器解压,这样会快一些。另外安装依赖的时候一定要带上--production这个参数。

version: "3"
services:
  instructor-nodejs:
    image: "node:19-bullseye-slim"
    working_dir: /home/app
    container_name: instructor-nodejs
    ports:
      - "3078:3078"
    volumes:
      # 挂载日志
      - ./logs:/home/app/logs
      # 挂载配置文件
      - ./config:/home/app/config
      # 挂载各种第三方插件
      - ./node_modules:/home/app/node_modules
      # 挂载主要程序
      - ./dist:/home/app/dist
    command: node /home/app/dist/main.js
    environment:
      RUNNING_ENV: 'prod'

结论

千万不要被Java的小伙伴给鄙视了。一个JDK镜像也就500M。拿着1G镜像去发布依赖,肯定不是太妥当的。