跳到主要内容

NestJS 容器部署

简化 Node.js/Nest.js 部署:使用 Docker 和 Google Cloud 的分步指南

一份修正后的指南,介绍 Node.js 和 NestJS 多阶段容器、不可变产物,以及为 Cloud Run 交付选择 Docker Hub 还是 Cloud Build。

阅读时间:8分钟作者

部署 Node.js 或 NestJS 服务

这是我在 2023 年为 NestJS 微服务采用的部署路径:构建 Docker 镜像,通过 GitHub Actions 发布,再部署到 Cloud Run。我已修正原指南中如今不会再用于生产交付的部分。

Docker 将运行时、依赖项和应用打包进一个镜像。它减少了本地、CI 和 Cloud Run 环境之间的差异,但配置与基础设施仍可能不同。

本指南分为三个阶段:构建并测试镜像,通过 GitHub Actions 和 Docker Hub 发布镜像,以及通过手动修订版本或持续部署将镜像部署到 Google Cloud。

前提条件

  • 开始构建前,请先安装 Docker。
  • 在 Windows 上安装 WSL(适用于 Linux 的 Windows 子系统),以便 Docker 使用所需的 Linux 环境。
  • 如果安装 WSL 时出现错误 0x80370114,请先启用所需的虚拟化功能。

阶段 1:创建 Docker 镜像

最初的 2023 年指南只使用一个 Node 18 Alpine 阶段,先安装生产依赖项,再在同一镜像中运行 Nest 构建。这个顺序无法可靠构建 NestJS,因为 TypeScript 和其他构建工具通常位于 devDependencies 中。修正后的镜像将编译与运行时分离,使构建阶段拥有完整工具链,同时不把这些工具带入生产环境。

FROM node:18-alpine AS build
WORKDIR /usr/src/app

# Copy manifests first so dependency layers can be reused.
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM node:18-alpine AS runtime
ENV NODE_ENV=production
WORKDIR /usr/src/app

COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=build /usr/src/app/dist ./dist

EXPOSE 8080
USER node
CMD ["node", "dist/main.js"]

构建阶段使用 npm ci,因此代码仓库中必须提交锁文件,并通过项目固定的 Nest 和 TypeScript 版本完成编译。运行时阶段接收生产依赖项和编译产物,然后使用镜像中的 node 用户放弃 root 权限。

Dockerfile
.dockerignore
node_modules
npm-debug.log
dist

忽略这些文件可以缩小 Docker 构建上下文,并防止主机依赖项或现有 dist 目录被复制进镜像。

在本地运行并检查容器

构建镜像并确认 Docker 已成功创建它。

docker build --tag nest-microservice .
docker images

启动一个分离模式的容器,将其内部端口 8080 映射到本地端口 5000。发布镜像前,请检查 docker ps、调用 HTTP 健康检查端点并查看 docker logs。

docker run -d --publish 5000:8080 --name nest-container nest-microservice
docker ps
docker ps --all
docker stop nest-container

确认本地镜像和容器正常运行后,将 Dockerfile 和 .dockerignore 推送到 GitHub 代码仓库。

阶段 2:使用 GitHub Actions 发布镜像

持续集成需要一个包含 Node.js 或 NestJS 源代码的 GitHub 代码仓库和一个 Docker Hub 账户。创建 Docker Hub 个人访问令牌,然后将账户名和令牌分别保存为名为 DOCKERHUB_USERNAME 和 DOCKERHUB_TOKEN 的代码仓库密钥。

下面这个 2023 年工作流会在每次推送到 main 时运行:检出源代码,登录 Docker Hub,启用 Buildx,使用根目录下的 Dockerfile 构建镜像,并推送 abdullahraheel/nest-microservice:latest。这里保留的是原始实现,包括当时使用的 Action 版本。

name: ci
on:
  push:
    branches:
      - "main"
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Login to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: ${{ secrets.DOCKERHUB_USERNAME }}/nest-microservice:latest
  1. 保存并提交该工作流。
  2. 将更改推送到 main,以启动工作流。
  3. 检查 Actions 运行记录;如果任务失败,请诊断具体失败步骤。
  4. 确认已发布的镜像仓库和标签出现在 Docker Hub 中。

阶段 3:将镜像部署到 Google Cloud Run

创建 Google Cloud 项目,打开 Cloud Run 并创建服务。手动部署时,以 username/repository:tag 的形式提供 Docker Hub 镜像,选择区域和服务设置,然后创建修订版本。示例镜像为 abdullahraheel/nest-microservice:latest;请使用你自己的工作流生成的确切镜像仓库和标签。

连接 Cloud Build 以实现持续部署

  1. 选择“创建服务”,再选择使用 Cloud Build 设置持续部署。
  2. 选择代码仓库提供商和代码仓库。
  3. 选择部署分支,本例使用 main。
  4. 选择 Dockerfile 作为构建类型,并提供其路径。
  5. 保存配置并创建服务。

连接生效后,合并到 main 的更改会触发 Cloud Build 构建代码仓库并自动重新部署服务。这是阶段 2 所产出 Docker Hub 镜像部署路径的替代方案,而不是该产物的另一个使用方。

总结与参考资料

  1. 将 NestJS 或 Node.js 项目构建为 Docker 镜像,并在本地测试其容器。
  2. 选择一条交付路径:通过 GitHub Actions 发布带版本的镜像,或让 Cloud Build 构建已连接的代码仓库。
  3. 手动部署确切的镜像修订版本,或使用已连接的 Cloud Build 路径自动创建服务修订版本。
  4. 定时工作应使用外部调度器或单例控制;Cloud Run Web 实例本身并不是单一且持久的 cron 工作进程。