cover

容器化部署 Next.js 和 Nest.js 应用

本文介绍了使用容器部署 Next.js 和 Nest.js 应用的方法

2023-10-29

如果使用最基础的方式部署 Next.js 和 Nest.js 应用,会导致镜像体积过大进而影响到镜像构建的效率。

通过将 Next.js 和 Nest.js 应用打包、移除无关依赖并使用体积更小的基础镜像作为运行环境,能有效地减少镜像体积,提高镜像构建效率。

heading

Next.js 应用的镜像文件

Next.js@12 提供了一个

模式,通过在
next.config.js
中设置该选项,在执行
next build
指令后可以自动创建一个独立文件夹,只复制生产部署所需的必要文件,大幅减少应用体积。

首先修改

next.config.js
配置:

const nextConfig = { // ... output: "standalone", // ... } module.exports = nextConfig
next.config.js

如果有使用

next/image
的需求,需要显式地将
sharp
添加为应用的依赖:

npm_config_sharp_binary_host="https://registry.npmmirror.com/-/binary/sharp" \ npm_config_sharp_libvips_binary_host="https://registry.npmmirror.com/-/binary/sharp-libvips" \ npm install sharp --registry=https://registry.npmmirror.com

添加一个编译脚本:

#!/bin/sh npx next build cp -r public .next/standalone/public # 复制 public 文件夹 cp -r .next/static .next/standalone/.next/static # 复制 static 文件夹 cp .env .next/standalone/.env # 复制环境配置文件
.scripts/build.sh

如果应用是 MonoRepo 的一部分,需要配置

,并调整
.scripts/build.sh
:

npx next build cp -r public .next/standalone/<root/packages/app>/public cp -r .artifacts .next/standalone/<root/packages/app>/.artifacts cp -r .next/standalone/node_modules .next/standalone/<root/packages/app>/node_modules cp -r .next/static .next/standalone/<root/packages/app>/.next/static cp .env .next/standalone/<root/packages/app>/.env
.scripts/build.sh

在项目根目录添加

Dockerfile

# 使用标准的基础镜像用于编译 FROM node:18 AS builder ARG ENV WORKDIR /app COPY package.json package-lock.json /app # 或者 yarn.lock RUN --mount=type=cache,target=/root/.npm,id=npm_cache,sharing=locked \ npm install --registry="https://registry.npmmirror.com" COPY . /app COPY .artifacts/.env.${ENV} /app/.env # 如果有 build time 配置,需要复制 .env 文件 RUN bash .scripts/build.sh # 执行编译 # 使用 alpine 镜像作为 Runtime 镜像 FROM node:18-alpine EXPOSE 3000 ENV PORT=3000 WORKDIR /app RUN --mount=type=cache,target=/tmp/dist,from=builder,source=/app/.next \ cp -r /tmp/dist/standalone/. /app CMD ["node", "server.js"]
Dockerfile

编译镜像:

docker build . -t next_app --build-arg ENV=staging

镜像大小:

Next.js 应用镜像大小

heading

Nest.js 应用的镜像文件

虽然 Nest.js 官方并没有提供类似 Next.js 的

standalone
的编译模式,但是可以通过
@vercel/ncc
实现类似的结果。

ncc
使用起来非常简单,并且不需要额外的配置就可以实现应用的打包和移除无关依赖。

首先在 Nest.js 应用中添加

@vercel/ncc
依赖:

npm install -D @vercel/ncc

添加编译脚本:

npx ncc build src/main.ts -o dist cp .env dist/.env
.scripts/build.sh

在应用根目录添加如下的 Dockerfile:

FROM node:18 as builder WORKDIR /app ARG ENV COPY package.json package-lock.json /app RUN --mount=type=cache,target=/root/.npm,id=npm_cache \ npm install --registry="https://registry.npmmirror.com" COPY . /app COPY .artifacts/.env.api.${ENV} /app/packages/api/.env # build references if necessary RUN bash .scripts/build.sh FROM node:18-alpine EXPOSE 3000 WORKDIR /app RUN --mount=type=cache,target=/tmp/dist,from=builder,source=/app/dist \ cp -r /tmp/dist/. /app CMD ["node", "index.js"]
Dockerfile

编译镜像:

docker build . -t nest_app --build-arg ENV=staging

镜像大小:

Nest.js 应用镜像大小

如果 Nest.js 应用是 MonoRepo,则在执行

ncc build
之前,应当将其在该 MonoRepo 下的其他依赖先编译完成(如果使用 lerna 管理 MonoRepo,则可以先使用
lerna run build --scope ... --parallel
将依赖先编译)。

如果在应用中使用了 TypeORM,并且遇到

no metadata for entity was found
错误。可能的原因是在注入 TypeORM 时的
entities
字段配置有误,确保将
autoLoadEntities
字段设置为
true

@Module({ imports: [ // ... TypeOrmModule.forRootAsync({ imports: [ConfigModule], useFactory: (cs: ConfigService) => { const config = { type: cs.get("DB_TYPE"), host: cs.get("DB_HOST"), port: cs.get<number>("DB_PORT"), username: cs.get("DB_USERNAME"), password: cs.get("DB_PASSWORD"), database: cs.get("DB_NAME"), // 自动加载 Entites autoLoadEntities: true, // 指定定义 Entity 的文件夹相对 src 的路径,或将 Entity Class 逐个加入其中 entities: ["schema/*{.ts,.js}"], synchronize: true, } as any; return config; }, inject: [ConfigService], }), // ... ] } export class AppModule {}
src/app.module.ts