本地使用docker compose deploy Nextjs+monorepo项目

607 阅读4分钟

在文章中,笔者打算使用pnpmworkspaces功能实现monorepo架构。接着使用docker compose来部署Nextjs项目。

monorepo项目搭建

安装pnpm

npm i pnpm -g

创建新项目

mkdir nextjs-monorepo
cd nextjs-monorepo
pnpm init

在根目录创建pnpm-workspace.yaml。参考如下内容:

packages:
  # 存放应用的文件夹
  - 'apps/*'
  # 存放包的文件夹
  - 'packages/*'

创建nextjs应用

进入apps目录,创建nextjs应用:

pnpx create-next-app@latest
//根据自己的需求选择
What is your project named? # web (change to whatever you want)
Would you like to use TypeScript with this project? # Yes
Would you like to use ESLint with this project? # Yes
Would you like to use Tailwind CSS with this project? Yes # Yes
Would you like to use `src/` directory with this project? # Yes
Use App Router (recommended)? # Yes
Would you like to customize the default import alias? # No

这里创建了一个name为web的nextjs应用。接着就可以通过下面命令运行起来了:

cd web
pnpm dev
//或者在根目录
pnpm --filter web dev

创建package

进入packages文件夹,创建一个core文件夹存放一些核心或者公共的代码:

cd packages
mkdir core
cd core
pnpm init

core/src/utils下创建一个common.ts文件,存放一些公共的函数:

export const sayHello = ()=>{
    console.log('hello');
}

由于使用typescript,需要在core目录下创建tsconfig.json文件:

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig to read more about this file */

    /* Language and Environment */
    "target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
    "lib": [
      "ES6",
      "DOM"
    ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
    /* Modules */
    "module": "ESNext" /* Specify what module code is generated. */,
    "rootDir": "./src" /* Specify the root folder within your source files. */,
    "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
    "resolveJsonModule": true /* Enable importing .json files. */,
    "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */,

    "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,

    "outDir": "build" /* Specify an output folder for all emitted files. */,

    "declarationDir": "./types" /* Specify the output directory for generated declaration files. */,

    // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
    "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
    "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,

    /* Type Checking */
    "strict": true /* Enable all strict type-checking options. */,
    "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */,

    /* Completeness */
    "skipLibCheck": true /* Skip type checking all .d.ts files. */,

    "jsx": "react"
  },
  "include": ["src/**/*"]
}

然后安装typescript来编译ts文件:

pnpm add typescript -F core

接着在package.json文件添加相关的script命令:

{
  "name": "core",
  "version": "1.0.0",
  "description": "core code",
  "files": [
    "build"
  ],
  "main": "./build/index.js",
  "types": "./build/index.d.ts",
  "scripts": {
    "build": "tsc",
    "typecheck": "tsc --noEmit"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "typescript": "^5"
  }
}

运行pnpm build就可以编译ts文件到build目录。

web使用core的包

web项目的package.json文件中添加对core的依赖:

dependencies:{
    //...
    "core":"workspace:*"
}

然后运行pnpm installcore软连接到web中。 最后就可以直接引入使用:


import { sayName } from "core/build/utils/common";
console.log(sayName("world"));

docker compose

安装docker compose

直接去[官网]下载对应系统的安装包即可。

编写yml文件

在项目根目录下新建一个docker-compose.yml文件:

services:
  web:
    container_name: web
    build:
      context: ./
      dockerfile: ./apps/web/Dockerfile
    ports:
      - 3002:3002

编写Dockerfile 文件

FROM node:18-alpine AS base

# apps/web/Dockerfile

# 1. 安装所需要的基础环境
FROM base AS  deps-and-builder
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache g++ make py3-pip libc6-compat

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

WORKDIR /app

# 通过package和lock文件来安装依赖
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY ./apps/web ./apps/web
COPY ./packages ./packages

RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile

RUN echo "pnpm install completed"
# 打包
RUN pnpm --filter web build


# 3. 运行阶段,copy所需要的文件
FROM base AS runner
WORKDIR /app

ENV NODE_ENV=production

RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001



# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
# 这里需要保持standalone的目录结构
COPY --from=deps-and-builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=deps-and-builder /app/apps/web/public ./apps/web/public
COPY --from=deps-and-builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static

#可通过该命令查看是否copy成功
RUN ls -la /app/apps/web

USER nextjs

EXPOSE 3002

ENV PORT=3002

ENV HOSTNAME="0.0.0.0"

CMD ["node", "apps/web/server.js"]

上面的Dockerfile有几点值得注意:

  1. 有时候COPY文件的时候会报错,请确保目录的结构是否保持一致,可能copy的时候放错地方,或者文件路径不存在。可以通过RUN ls -la your path来调试(可以在docker的可视化客户端查看打印日志)。
  2. 在COPY整个项目或者目录的时候,可以通过.dockerignore文件来排除不想COPY的文件或目录。
  3. nextjs应用需要在next.config.mjs文件中配置output类型为standalone。建议先运行build命令然后查看standalone的目录结构,后面COPY public和static的文件夹需要跟standalone的目录结构保持一致。
const nextConfig = {
    output: "standalone",
}

运行docker compose

可以通过下面的命令来启动docker compose:

# 启动
docker compose -f docker-compose.yml up -d --build
# 停止
docker compose -f docker-compose.yml down

或者创建一个Makefile文件,编写以下内容:

.PHONY: docker-start
docker-start:
	docker compose -f docker-compose.yml up -d --build

.PHONY: docker-stop
docker-stop:
	docker compose -f docker-compose.yml down
# 启动
make docker-start
# 停止
make docker-stop

启动后可以通过docker ps来查看是否成功