构建和部署 Monorepo WebSocket Web 应用程序
介绍
**提示:**本文仅关注构建和部署阶段以及我遇到的问题和解决方案。对于项目本身,请随意参考我 github 上的完整存储库。
先决条件
步骤
首先,由于我使用 pnpm 作为项目的包管理器,因此我遵循了此说明使用 Docker
创建并构建我的 Dockerfile。
Dockerfile
FROM node:23-slim AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable RUN corepack pnpm --version RUN pnpm add -g serve FROM base AS build COPY . /usr/src/app WORKDIR /usr/src/app RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile RUN pnpm run -r build RUN pnpm deploy --filter=backend --prod /prod/backend RUN pnpm deploy --filter=web --prod /prod/web # backend FROM base AS backend COPY --from=build /prod/backend /prod/backend WORKDIR /prod/backend EXPOSE 8000 CMD [ "pnpm", "start" ] # web FROM base AS web COPY --from=build /prod/web /prod/web WORKDIR /prod/web EXPOSE 5173 CMD [ "serve", "-s", "dist", "-l", "5173" ]
在本地机器上构建 Docker 镜像并运行 Docker 容器
后端
docker build . --target backend --tag backend:latest
前端
docker build . --target web --tag web:latest
因为WebSocket应用的客户端需要连接到服务端,而服务端需要允许WebSocket客户端跨域资源共享,所以需要允许运行在Docker容器上的服务相互通信。
在本地机器上,创建自定义的用户定义网络“my-net”
docker network create -d bridge my-net
在创建的网络上运行后端容器
docker run --network=my-net -d -p 8000:8000 backend:latest
在创建的网络上运行 Web 容器
docker run --network=my-net -d -p 5173:5173 web:latest
但是,由于 Render 免费套餐的限制,除了设置用户定义网络之外,还需要其他解决方案。
在 Render 上部署 Web 服务
解决方案 1:使用基础设施即代码
参考渲染蓝图 (IaC)
**注意:**我没有选择这种方法,因为目前需要信用卡信息
services: - type: web runtime: docker name: backend envVars: - key: SERVER_PORT value: "8000" - key: CLIENT_URL value: "http://frontend.onrender.com" dockerCommand: | docker build . --target backend --tag backend:latest && \ docker run -d -p 8000:8000 backend:latest - type: web runtime: docker name: frontend envVars: - key: VITE_SERVER_URL value: "http://backend.onrender.com" dockerCommand: | docker build . --target web --tag web:latest && \ docker run -d -p 5173:5173 web:latest
解决方案2:将Dockerfile分为2部分,分别用于后端和前端
由于 Render 会自动根据目标 repo 的 Dockerfile 运行 docker build,为了创建 2 个 web 服务并允许它们相互通信,请根据上述 Dockerfile 在每个项目中创建 2 个 Dockerfile:
应用程序/后端/Dockerfile
FROM node:23-slim AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable RUN corepack pnpm --version FROM base AS build COPY . /usr/src/app WORKDIR /usr/src/app RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile RUN pnpm run -r build RUN pnpm deploy --filter=backend --prod /prod/backend FROM base AS backend COPY --from=build /prod/backend /prod/backend WORKDIR /prod/backend EXPOSE 8000 CMD [ "pnpm", "start" ]
应用程序/网络/Dockerfile
FROM node:23-slim AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable RUN corepack pnpm --version RUN pnpm add -g serve FROM base AS build COPY . /usr/src/app WORKDIR /usr/src/app RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile RUN pnpm run -r build RUN pnpm deploy --filter=web --prod /prod/web FROM base AS web COPY --from=build /prod/web /prod/web WORKDIR /prod/web EXPOSE 5173 CMD [ "serve", "-s", "dist", "-l", "5173" ]
然后按照说明部署这两个Web服务
**笔记:**
部署之前,这里设置环境变量,分别设置后端服务和前端服务的客户端url和服务端url,如下图
将客户端 URL 作为环境变量设置为后端服务

将服务器 URL 作为环境变量设置为客户端服务

问题与解决方案
1. 错误:tsconfig.json:4:5 - 错误 TS6310: 引用的项目 '/socket-react-fullstack-monorepo/apps/web/tsconfig.app.json' 可能无法禁用发射
根本原因
待定
解决方案
将 ``files": [],` 添加到 apps/web/tsconfig.json
2. 错误:ERR_PNPM_LOCKFILE_BREAKING_CHANGE 锁文件 /usr/src/app/pnpm-lock.yaml 与当前 pnpm 不兼容
在构建 docker 文件期间`RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile`
根本原因
因为 Dockerfile 中的 `RUN corepack` enable 下载的是 pnpm 版本 8.15.6,但是执行 `pnpm i` 生成的 pnpm-lock.yaml 文件却是本地安装的 pnpm 包(通过 `npm install -g pnpm` )生成的,版本是 9.15.1,所以版本不匹配会报错

解决方案
更改(降级)本地安装的 pnpm 版本,使其与 Dockerfile 中 corepack 下载的目标版本(即 8.15.6)相匹配,并使用 pnpm 的目标版本生成新的 pnpm-lock.yaml
npm install -g pnpm@8.15.6
安装完成后,检查 pnpm 版本,确保它显示正确的目标版本(这里是 8.15.6)
pnpm --version
删除本地 pnpm-lock.yaml
rm pnpm-lock.yaml
执行pnpm install命令,生成新的rm pnpm-lock.yaml文件
pnpm i
3. 错误:执行命令 pnpm run -r build 和 pnpm deploy --filter=backend --prod /prod/backend 后,/prod/backend 中没有 dist/ 文件夹
根本原因
package.json 中缺少“files”字段
解决方案
在 apps/backend/package.json 中添加文件,然后再次运行 `pnpm deploy`
"files": [ "dist" ],
4. 错误:找不到模块‘tslib’
尝试使用命令运行容器后
docker run -d -p 8000:8000 backend:latest
根本原因
apps/backend/package.json 中的 tslib 包位于 `devDependencies` 中,但根据 tslib 的说法,它应该位于 `dependencies` 中
... "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/node": "^20.14.12", "nodemon": "^3.1.4", "tslib": "^2.6.3", "typescript": "^5.3.3" }, ...
解决方案
将“tslib”移至“dependencies”并运行“pnpm i”以根据 apps/backend/package.json 中的新依赖项生成 pnpm-lock.yaml
... "dependencies": { "tslib": "^2.6.3", "cors": "^2.8.5", "express": "^4.19.2", "socket.io": "^4.7.5", "ts-node": "^10.9.2", "zod": "^3.23.8" } ...
5. 错误:ERR_PNPM_NO_SCRIPT_OR_SERVER 缺少脚本启动或文件 server.js
尝试在 Docker 容器中使用“pnpm start”启动 Vite 应用程序时
根本原因
apps/web/package.json 中的脚本中没有“start”
"scripts": { "dev": "vite --clearScreen false", "build": "tsc && vite build", "preview": "vite preview", "lint": "eslint \"src/**/*.ts\"" },
解决方案
使用 **serve** 来提供静态文件
在 Dockerfile 中全局安装服务
RUN pnpm add -g serve
更改此
... CMD [ "pnpm", "start" ]
到
... CMD [ "serve", "-s", "dist", "-l", "5173" ]
再次以分离模式(`-d`)构建并运行 Docker 容器,使用 `web:latest` 镜像将主机上的端口 5173 映射到容器中的端口 5173
docker run -d -p 5173:5173 web:latest
资源
如何调试正在运行的 Docker 容器:使用 docker container exec
例如,检查正在执行的docker容器中是否存在某些环境变量。
列出所有正在运行的docker容器,并查看目标容器的容器ID
docker ps
使用容器ID分配目标容器的伪TTY(这里的ae980e452cbe是容器ID)
docker exec -it ae980e452cbe /bin/sh
列出所有环境变量
env
示例输出
# env NODE_VERSION=23.5.0 ... PWD=/prod/backend PNPM_HOME=/pnpm #