Nest in docker
在 Nest.js 项目中,为了部署到服务器上,Dockers 镜像是必不可少的。而 Nest 本身并没有带有 dockerfile,所以这一部分需要我们自己手动创建。现在我们尝试为一个 Nest 项目书写 DockerFile。
项目准备
使用 Nest CLI 创建一个全新的 Nest 项目
npm i -g @nestjs/cli
nest new project-name
复制代码
项目结构如下图:
编译
在本机调试 Nest 时可以直接运行 TS 代码,但实际上 node.js 并不是一个 TS 的运行时,所以事实上 Nest 是依靠 ts-node 将代码在内存中编译为了 js 代码再运行。而在服务器上代码是不会变动且不需要调试的,所以没有必要继续使用 TS 代码,而应该将其编译为 JS 代码。
yarn build
复制代码
得到 dist 文件夹:
编写最简 dockerfile
新建 dockerfile 文件
FROM node:lts-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY ./dist .
EXPOSE 3000
CMD ["node", "main.js"]
复制代码
为 docker 中添加 node_modules
最简镜像是无法使用的,因为没有 node_modules 的支持,项目是无法启动的。也不能将本机的 node_modules 拷贝进 docker 中,不同操作系统不同环境的 node_modules 不能混用。正确的做法是添加 package.json 文件放入 docker,再下载依赖,然后再将其删除(也可以不删,不过之后这个文件就没有用了)。
FROM node:lts-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY ["package.json", "yarn.lock", "./"]
RUN yarn --production --silent
RUN rm package.json yarn.lock
COPY ./dist .
EXPOSE 3000
CMD ["node", "main.js"]
复制代码
限定文件的拷贝
虽然能够通过访问 http://localhost:3000/
得到 Hello World!
,但观察 docker 镜像发现文件结构如下:
很容易发现这里面有个很大的问题:多出很多非 .js 文件,增加了 docker 的空间。事实上 .ts.d 和 .js.map 是调试用的文件,而 tsconfig.build.tsbuildinfo 是编译信息,这些文件再运行时是不需要的。虽然修改 tscofig.josn 文件可以不生成这些文件,但又会影响调试,所以不推荐这么做。最简单的做法是在拷贝文件时只拷贝 js 和 json(某些配置会在 json 文件中) 文件。
FROM node:lts-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY ["package.json", "yarn.lock", "./"]
RUN yarn --production --silent
RUN rm package.json yarn.lock
COPY ["./dist/*.js", "./dist/*.json", "./"]
EXPOSE 3000
CMD ["node", "main.js"]
复制代码
之后再观察文件夹发现只会得到纯净的运行文件
子目录的拷贝
随着项目的开发,目录结构不再是单层的文件,层级会变得复杂,我们希望最终镜像中的 JS 文件需要保持和原始 TS 一样的目录层级和结构。
假如我们的项目引入了 cats 模块,因此创建了子文件 cats。这是原始文件夹:
编译后的 dist 文件夹如下
但是在 docker 镜像中并没有 cats 文件夹
COPY 命令仅仅拷贝文件夹下的文件,并没有拷贝文件夹,更没有保持目录层级。要保持文件层级只有在 copy 的时候使用 .dist/
,但这样又会导致多拷贝文件,除了选择要拷贝什么文件之外,还有一种办法是使用 .dockerignore
文件反过来将不需要的文件排除出去。dockerfile 如下:
FROM node:lts-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY ["package.json", "yarn.lock", "./"]
RUN yarn --production --silent
RUN rm package.json yarn.lock
COPY ./dist/ .
EXPOSE 3000
CMD ["node", "main.js"]
复制代码
然后在 .dockerignore
文件中添加
**/*.js.map
**/*.d.ts
**/tsconfig.build.tsbuildinfo
复制代码
之后再构建就能得到一个既保持了文件层级又只有 js 和 json 文件的 docker 镜像。
用起来
dockerfile 写完之后需要在 CI 上用起来,但并不是简单的直接就用的。下载好代码之后,运行 docker build
之前还是需要先运行 yarn lint
yarn build
yarn test
。还有就是依赖缓存,如果不做的话每次继承都需要去 npm 源下载依赖。这些都做完了之后,一个简易的 CI 就搭建好了。