- 新增 PostgreSQL 备份、fresh DB 恢复和 server_data volume 备份脚本\n- 恢复默认拒绝覆盖,必须显式 --confirm-overwrite\n- 补充 package scripts、deploy verify 校验和部署文档\n\nCo-Authored-By: GPT-5 Codex <codex@openai.com>
306 lines
9.8 KiB
Markdown
306 lines
9.8 KiB
Markdown
# 部署指南
|
||
|
||
本文档覆盖三种部署方式:
|
||
|
||
- 本地开发:前端、后端直接用 pnpm 跑,PostgreSQL 和 Redis 用 `docker-compose.yml` 启动。
|
||
- 本地服务器部署:一台本机或局域网服务器用 `docker-compose.local.yml` 跑完整 Web、Server、PostgreSQL、Redis、Nginx。
|
||
- 云服务器生产:所有服务用 `docker-compose.prod.yml` 编排,Nginx 对外暴露 HTTP,业务数据写入 PostgreSQL volume,AI 配置文件写入 server volume。
|
||
|
||
## 本地开发部署
|
||
|
||
```bash
|
||
pnpm install
|
||
cp apps/server/.env.example apps/server/.env
|
||
docker compose up -d
|
||
pnpm db:migrate
|
||
pnpm dev
|
||
```
|
||
|
||
访问地址:
|
||
|
||
- Web: `http://localhost:3000`
|
||
- API: `http://localhost:3001/api/v1`
|
||
- API 健康探测:`http://localhost:3001/api/v1/config/ai`
|
||
|
||
## 本地服务器部署
|
||
|
||
本地服务器部署适合这些场景:
|
||
|
||
- 在自己的电脑上用接近生产的方式跑完整系统。
|
||
- 在办公室内网或测试机上给同事访问。
|
||
- 不想安装 Node/pnpm 到目标机器,只想用 Docker Compose 管理全栈服务。
|
||
|
||
它不同于“本地开发”:不会用 `pnpm dev`,而是构建生产镜像并通过 Nginx 统一入口访问。
|
||
|
||
### 1. 准备配置
|
||
|
||
```bash
|
||
cp .env.local-server.example .env.local-server
|
||
```
|
||
|
||
默认访问地址是:
|
||
|
||
```text
|
||
http://localhost:8080
|
||
```
|
||
|
||
如果要给局域网其他人访问,修改 `.env.local-server`:
|
||
|
||
```env
|
||
LOCAL_ACCESS_HOST=192.168.1.50
|
||
NEXTAUTH_URL=http://192.168.1.50:8080
|
||
```
|
||
|
||
其中 `192.168.1.50` 换成这台机器在局域网里的 IP。`NEXT_PUBLIC_API_URL` 保持 `/api/v1`,让浏览器通过同一个 Nginx 入口访问后端。
|
||
|
||
### 2. 校验并启动
|
||
|
||
```bash
|
||
pnpm deploy:verify
|
||
pnpm deploy:local:build
|
||
pnpm deploy:local:up
|
||
```
|
||
|
||
首次启动后执行数据库 migration:
|
||
|
||
```bash
|
||
docker compose --env-file .env.local-server -f docker-compose.local.yml exec server pnpm --filter server db:deploy
|
||
```
|
||
|
||
检查服务:
|
||
|
||
```bash
|
||
docker compose --env-file .env.local-server -f docker-compose.local.yml ps
|
||
curl http://localhost:8080/api/v1/config/ai
|
||
```
|
||
|
||
停止本地服务器部署:
|
||
|
||
```bash
|
||
pnpm deploy:local:down
|
||
```
|
||
|
||
本地服务器部署使用独立 volume:
|
||
|
||
- `local_postgres_data`
|
||
- `local_redis_data`
|
||
- `local_server_data`
|
||
|
||
这些 volume 与云服务器生产部署的 `postgres_data`、`redis_data`、`server_data` 分开,避免本机测试数据和生产数据混在一起。
|
||
|
||
## 云服务器生产部署
|
||
|
||
### 1. 准备服务器
|
||
|
||
推荐一台 2 核 4G 以上 Linux 云服务器,并安装:
|
||
|
||
- Docker Engine
|
||
- Docker Compose v2
|
||
- Git
|
||
|
||
开放 80 端口。如果 HTTPS 由云厂商负载均衡、CDN 或宝塔/Nginx 外层托管,则在外层终止 TLS 后转发到本机 80。
|
||
|
||
### 2. 准备配置
|
||
|
||
```bash
|
||
cp .env.production.example .env.production
|
||
```
|
||
|
||
必须修改这些值:
|
||
|
||
- `POSTGRES_PASSWORD`:强密码。
|
||
- `DATABASE_URL`:密码要与 `POSTGRES_PASSWORD` 一致,主机保持 `postgres`。
|
||
- `SERVER_NAME`:你的域名,例如 `pm.example.com`。
|
||
- `NEXTAUTH_URL`:正式访问地址,例如 `https://pm.example.com`。
|
||
- `NEXTAUTH_SECRET`:随机长密钥。
|
||
- `NEXT_PUBLIC_API_URL`:同域部署保持 `/api/v1`;如果前后端分域,填完整 API 地址。
|
||
- `ANTHROPIC_API_KEY`:可先留空或占位,后续也可以在 `/admin/ai-config` 页面配置。
|
||
|
||
### 3. 校验部署文件
|
||
|
||
```bash
|
||
pnpm deploy:verify
|
||
```
|
||
|
||
该命令会检查 `Dockerfile.web`、`Dockerfile.server`、`docker-compose.prod.yml`、`docker-compose.local.yml`、Nginx 模板、env 示例和本文档是否齐全。
|
||
|
||
### 4. 构建并启动
|
||
|
||
```bash
|
||
docker compose --env-file .env.production -f docker-compose.prod.yml build
|
||
docker compose --env-file .env.production -f docker-compose.prod.yml up -d
|
||
```
|
||
|
||
首次启动后执行数据库迁移:
|
||
|
||
```bash
|
||
docker compose --env-file .env.production -f docker-compose.prod.yml exec server pnpm --filter server db:deploy
|
||
```
|
||
|
||
然后检查服务状态:
|
||
|
||
```bash
|
||
docker compose --env-file .env.production -f docker-compose.prod.yml ps
|
||
curl http://localhost/api/v1/config/ai
|
||
```
|
||
|
||
## 全自动生产发布(推荐)
|
||
|
||
生产环境推荐走 `.github/workflows/deploy-production.yml`,不再要求部署侧手工执行 `docker compose build` 或手工判断是否需要 `up`。
|
||
|
||
GitHub 仓库需要配置这些 Secrets:
|
||
|
||
- `PROD_HOST`:生产服务器地址。
|
||
- `PROD_USER`:SSH 用户。
|
||
- `PROD_SSH_KEY`:SSH 私钥。
|
||
- `PROD_APP_DIR`:服务器上的项目目录。
|
||
- `GHCR_READ_TOKEN`:可选。GHCR 镜像为私有包时,用于服务器 `docker login ghcr.io` 拉镜像。
|
||
|
||
服务器首次准备仍然需要完成一次:
|
||
|
||
```bash
|
||
git clone <repo-url> ftb-project-management
|
||
cd ftb-project-management
|
||
cp .env.production.example .env.production
|
||
```
|
||
|
||
然后按实际环境改好 `.env.production` 里的数据库密码、域名、`NEXTAUTH_SECRET`、AI key 等。`APP_VERSION`、`APP_BUILD_TIME`、`WEB_IMAGE`、`SERVER_IMAGE` 会由 GitHub Actions 在每次发布时自动更新。
|
||
|
||
之后只要代码合并或 push 到 `master`,Actions 会自动执行:
|
||
|
||
1. 用当前 commit SHA 构建 `web` 和 `server` Docker 镜像。
|
||
2. 推送镜像到 GHCR,镜像 tag 同时包含 commit SHA 和 `master`。
|
||
3. SSH 到生产服务器,`git pull --ff-only origin master` 更新 Compose 和部署脚本。
|
||
4. 写入 `.env.production` 的 `APP_VERSION`、`APP_BUILD_TIME`、`WEB_IMAGE`、`SERVER_IMAGE`。
|
||
5. 执行 `docker compose --env-file .env.production -f docker-compose.prod.yml pull web server` 拉取本次 SHA 镜像。
|
||
6. 启动数据库与 Redis,执行 `pnpm --filter server db:deploy`。
|
||
7. 执行 `docker compose --env-file .env.production -f docker-compose.prod.yml up -d --remove-orphans` 重启服务。
|
||
8. 通过 `/api/v1/health/version` 校验运行中的后端版本是否等于本次 commit SHA。
|
||
|
||
如果最后一步失败,Actions 会红掉,说明“代码已合并”不等于“线上容器已更新”。本地或服务器也可以手工运行:
|
||
|
||
```bash
|
||
pnpm deploy:check-runtime http://localhost/api/v1/health/version <expected-commit-sha>
|
||
```
|
||
|
||
## Nginx 路由
|
||
|
||
`deploy/nginx/default.conf.template` 使用官方 Nginx 镜像的模板机制生成配置:
|
||
|
||
- `/api/` 转发到 `server:3001`
|
||
- `/` 转发到 `web:3000`
|
||
- `X-Forwarded-Proto`、`X-Forwarded-For` 等反向代理头会透传给后端
|
||
|
||
生产 Compose 默认只监听 HTTP 80。HTTPS 建议优先交给云负载均衡、CDN 或宿主机外层证书管理工具;如果要让本 Compose 内的 Nginx 直接处理 HTTPS,可以在后续增加证书 volume 和 443 server block。
|
||
|
||
## 数据持久化
|
||
|
||
生产 Compose 使用三个命名 volume:
|
||
|
||
- `postgres_data`:PostgreSQL 数据库,包含业务主数据和 `app_data`。
|
||
- `redis_data`:Redis AOF 数据。
|
||
- `server_data`:NestJS 写入的 AI Provider 配置文件,例如 `data/ai-config.json`。
|
||
|
||
浏览器不再作为业务数据主存储。清浏览器缓存不会删除产品、项目、版本、任务、测试用例、Bug 等业务数据。
|
||
|
||
## 备份与恢复
|
||
|
||
备份分两类:PostgreSQL 业务数据和 `server_data` volume 中的运行时配置。真实备份文件默认写到 `backups/`,该目录已加入 `.gitignore`,不要把 dump 或 tar 包提交到仓库。
|
||
|
||
先演练命令,不写文件:
|
||
|
||
```bash
|
||
pnpm backup:postgres -- --dry-run --env-file .env.production
|
||
pnpm backup:server-data -- --dry-run --env-file .env.production
|
||
```
|
||
|
||
执行正式备份:
|
||
|
||
```bash
|
||
pnpm backup:postgres -- --env-file .env.production
|
||
pnpm backup:server-data -- --env-file .env.production
|
||
```
|
||
|
||
PostgreSQL 备份使用 `pg_dump --format=custom --no-owner --no-acl`,便于跨环境恢复。`server_data` 备份使用只读 volume mount 和 `tar -czf`,覆盖 AI Provider 配置等后端运行时文件。
|
||
|
||
恢复数据库必须显式确认覆盖。默认命令会拒绝执行,防止误删现有库:
|
||
|
||
```bash
|
||
pnpm restore:postgres -- --env-file .env.production --input backups/postgres/ftb_pm-postgres-20260708T120000Z.dump
|
||
```
|
||
|
||
确认要把目标数据库重建为 fresh DB 后,再运行:
|
||
|
||
```bash
|
||
pnpm restore:postgres -- \
|
||
--env-file .env.production \
|
||
--input backups/postgres/ftb_pm-postgres-20260708T120000Z.dump \
|
||
--confirm-overwrite
|
||
```
|
||
|
||
恢复前建议先 dry-run 看清将执行的 `psql/dropdb/createdb/pg_restore` 步骤:
|
||
|
||
```bash
|
||
pnpm restore:postgres -- \
|
||
--dry-run \
|
||
--confirm-overwrite \
|
||
--env-file .env.production \
|
||
--input backups/postgres/ftb_pm-postgres-20260708T120000Z.dump
|
||
```
|
||
|
||
如果要恢复到临时库做校验,不覆盖当前生产库:
|
||
|
||
```bash
|
||
pnpm restore:postgres -- \
|
||
--env-file .env.production \
|
||
--input backups/postgres/ftb_pm-postgres-20260708T120000Z.dump \
|
||
--target-db ftb_pm_restore_check \
|
||
--confirm-overwrite
|
||
```
|
||
|
||
## 升级流程
|
||
|
||
```bash
|
||
git pull
|
||
pnpm deploy:verify
|
||
docker compose --env-file .env.production -f docker-compose.prod.yml build
|
||
docker compose --env-file .env.production -f docker-compose.prod.yml up -d
|
||
docker compose --env-file .env.production -f docker-compose.prod.yml exec server pnpm --filter server db:deploy
|
||
```
|
||
|
||
升级前建议先备份数据库:
|
||
|
||
```bash
|
||
pnpm backup:postgres -- --env-file .env.production
|
||
pnpm backup:server-data -- --env-file .env.production
|
||
```
|
||
|
||
## 常见排查
|
||
|
||
后端是否启动:
|
||
|
||
```bash
|
||
curl http://localhost/api/v1/config/ai
|
||
```
|
||
|
||
数据库是否可用:
|
||
|
||
```bash
|
||
docker compose --env-file .env.production -f docker-compose.prod.yml exec postgres pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"
|
||
```
|
||
|
||
查看日志:
|
||
|
||
```bash
|
||
docker compose --env-file .env.production -f docker-compose.prod.yml logs -f server
|
||
docker compose --env-file .env.production -f docker-compose.prod.yml logs -f web
|
||
docker compose --env-file .env.production -f docker-compose.prod.yml logs -f nginx
|
||
```
|
||
|
||
如果前端能打开但数据为空,优先检查:
|
||
|
||
1. `NEXT_PUBLIC_API_URL` 是否为 `/api/v1` 或正确的完整 API 地址。
|
||
2. `curl http://localhost/api/v1/config/ai` 是否返回 200。
|
||
3. `server` 日志里是否有 Prisma 或 `DATABASE_URL` 错误。
|
||
4. 是否已执行 `pnpm --filter server db:deploy`。
|