每次部署都无条件重建 backend + web 两个镜像(实测后端 3m35s、前端 2m54s), 并且每次都在迁移前 pg_dump + 传 OSS(生产 492MB dump 那一步约 12 分钟); 只改了后端也要把前端整条链重跑一遍。 改动: - 新增 deploy/lib/classify-changes.sh:把改动文件映射成"要重建哪一端", 规则保守——apps/backend/** → backend、apps/frontend/**|deploy/caddy/**|前端 Dockerfile → web、 文档/部署脚本/测试文件 → 不重建,compose 文件与未知路径 → 两端都重建。 - ubuntu-deploy.sh 用 .deploy-state(上次成功部署的 commit + .env 指纹)算 git diff, 未变化的一端不 build、也不 --force-recreate,容器保持运行;若它没在跑则用现有镜像拉起。 两端都要建时合并为一次 compose build(bake 并行)。 - 备份:改动不含 apps/backend/src/db/migrations/** 时自动跳过(--backup 强制、 --skip-backup 无条件跳过);算不出改动范围(首次使用、force push)时不自动跳过。 - compose pull 改 --policy missing:不再每次重新校验 postgres tag,避免顺手静默升级数据库镜像。 - 新增 --dry-run(打印计划后退出,含基础镜像 digest)、--all、--backend-only/--web-only; 部署结束打印各阶段耗时,回答"这次怎么比上次慢"不用靠推算。 - .deploy-state 进 .gitignore;README 与 docs/部署启动与数据库重建.md 同步。 验证:新增 7 个用例直接执行分类脚本断言"改动→重建范围",并守住"状态只在健康检查后才落盘" 等关键顺序;全量 595 个单测、typecheck、lint、check:sql 全通过。
311 lines
9.1 KiB
Markdown
311 lines
9.1 KiB
Markdown
# 部署启动与数据库重建
|
||
|
||
本文记录本地开发、数据库迁移、重建库和 Ubuntu 服务器部署的常用命令。
|
||
|
||
## 数据库迁移策略
|
||
|
||
项目使用 `node-pg-migrate`,迁移目录:
|
||
|
||
```text
|
||
apps/backend/src/db/migrations/
|
||
001_init.sql 空库基线(完整结构)
|
||
002_xxx.sql 后续增量(示例命名)
|
||
003_xxx.sql
|
||
...
|
||
```
|
||
|
||
记录表:`schema_migrations`。
|
||
|
||
| 场景 | 行为 |
|
||
| --- | --- |
|
||
| 全新空库 | 按序号执行 `001_init` → 全部增量 |
|
||
| 已有库升级 | 只执行尚未记录的增量文件 |
|
||
| 改历史迁移文件内容 | **不会**自动重跑;已应用文件禁止修改 |
|
||
|
||
约定:
|
||
|
||
1. `001_init.sql` 只作为基线;上线后结构变更不要改它。
|
||
2. 每次 schema 变更新增 `NNN_name.sql`(三位序号)。
|
||
3. 迁移与业务代码同一变更一起提交。
|
||
4. 后端启动会自动 `up`;也可用命令手动执行。
|
||
|
||
### 开发常用命令
|
||
|
||
```bash
|
||
# 查看本地文件与库内已应用记录
|
||
docker compose -f docker-compose.dev.yml exec -T backend npm run db:migrate:status
|
||
|
||
# 手动执行未应用迁移
|
||
docker compose -f docker-compose.dev.yml exec -T backend npm run db:migrate
|
||
|
||
# 创建下一份增量迁移模板
|
||
docker compose -f docker-compose.dev.yml exec -T backend npm run db:migrate:create -- add_column_example
|
||
```
|
||
|
||
宿主机直连(`postgres` 主机名仅在 Docker 网络内有效):
|
||
|
||
```bash
|
||
cd apps/backend
|
||
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/order_site npm run db:migrate:status
|
||
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/order_site npm run db:migrate
|
||
```
|
||
|
||
## 本地开发启动
|
||
|
||
首次:
|
||
|
||
```bash
|
||
cp .env.mac-docker.example .env
|
||
docker compose -f docker-compose.dev.yml up -d --build
|
||
```
|
||
|
||
日常:
|
||
|
||
```bash
|
||
docker compose -f docker-compose.dev.yml up -d
|
||
```
|
||
|
||
状态与日志:
|
||
|
||
```bash
|
||
docker compose -f docker-compose.dev.yml ps
|
||
docker compose -f docker-compose.dev.yml logs -f backend
|
||
docker compose -f docker-compose.dev.yml logs -f frontend
|
||
```
|
||
|
||
本地入口:
|
||
|
||
- 前端、后台、领取页:`http://localhost`
|
||
- 健康检查:`http://localhost/health`
|
||
- 就绪检查:`http://localhost/health/ready`
|
||
|
||
## 本地重建数据库
|
||
|
||
仅在需要清空开发数据,或本地库与迁移历史严重不一致时使用。
|
||
|
||
只重建开发库 volume:
|
||
|
||
```bash
|
||
docker compose -f docker-compose.dev.yml down
|
||
docker volume rm order_site_postgres_dev_data
|
||
docker compose -f docker-compose.dev.yml up -d --build
|
||
```
|
||
|
||
volume 名称不确定时:
|
||
|
||
```bash
|
||
docker volume ls | grep postgres_dev_data
|
||
```
|
||
|
||
删除当前 dev compose 全部 volume(会重装 node_modules volume):
|
||
|
||
```bash
|
||
docker compose -f docker-compose.dev.yml down -v
|
||
docker compose -f docker-compose.dev.yml up -d --build
|
||
```
|
||
|
||
确认迁移记录:
|
||
|
||
```bash
|
||
docker compose -f docker-compose.dev.yml exec -T backend npm run db:migrate:status
|
||
```
|
||
|
||
或:
|
||
|
||
```bash
|
||
docker compose -f docker-compose.dev.yml exec -T backend node --input-type=module -e '
|
||
const { query, closeDb } = await import("./src/db/client.ts");
|
||
try {
|
||
const result = await query("SELECT COALESCE(filename, name) AS name FROM schema_migrations ORDER BY COALESCE(filename, name)");
|
||
console.log(result.rows.map((row) => row.name).join("\n"));
|
||
} finally {
|
||
await closeDb();
|
||
}
|
||
'
|
||
```
|
||
|
||
## 本地验证
|
||
|
||
```bash
|
||
docker compose -f docker-compose.dev.yml exec -T backend npm test
|
||
docker compose -f docker-compose.dev.yml exec -T backend npm run typecheck
|
||
docker compose -f docker-compose.dev.yml exec -T backend npm run build
|
||
docker compose -f docker-compose.dev.yml exec -T frontend npm run typecheck
|
||
docker compose -f docker-compose.dev.yml exec -T frontend npm run build
|
||
```
|
||
|
||
## 服务器首次部署
|
||
|
||
建议 Ubuntu + Docker Compose。
|
||
|
||
```bash
|
||
cp .env.server.example .env
|
||
```
|
||
|
||
至少配置:
|
||
|
||
```text
|
||
APP_DOMAIN
|
||
CADDY_SITE_ADDR
|
||
WORKER_SITE_ADDR
|
||
CLAIM_BASE_URL
|
||
POSTGRES_PASSWORD
|
||
DATABASE_URL
|
||
ADMIN_SESSION_SECRET
|
||
ADMIN_DEFAULT_USERS_JSON
|
||
CORS_ALLOWED_ORIGINS
|
||
STORAGE_MODE
|
||
STORAGE_OSS_ENDPOINT
|
||
STORAGE_OSS_BUCKET
|
||
STORAGE_OSS_ACCESS_KEY_ID
|
||
STORAGE_OSS_SECRET_ACCESS_KEY
|
||
STORAGE_OSS_REGION
|
||
```
|
||
|
||
其中 `APP_DOMAIN`、`CORS_ALLOWED_ORIGINS` 与 `STORAGE_MODE=oss` / `STORAGE_OSS_*` 为生产必需:`docker-compose.yml:95` 的 `STORAGE_MODE` 生产默认即 `oss`,`apps/backend/src/config/runtime-validation.ts:107-114` 在生产环境强制校验 `STORAGE_OSS_*` 凭据并使用显式 CORS 域名白名单;`.env.server.example` 已给出这些变量的模板值,其中带 `change-me` 的占位会被 `deploy/ubuntu-deploy.sh` 的占位值检查拦住,需替换为真实值。
|
||
|
||
容器内连接 Compose PostgreSQL 时,主机名用 `postgres`:
|
||
|
||
```text
|
||
DATABASE_URL=postgres://postgres:你的数据库密码@postgres:5432/order_site
|
||
```
|
||
|
||
不要写成容器内的 `127.0.0.1`(那会指向后端容器自己)。
|
||
|
||
部署:
|
||
|
||
```bash
|
||
bash deploy/ubuntu-deploy.sh
|
||
```
|
||
|
||
脚本会:检查 Docker、检查 `.env` 占位值、检查 `DATABASE_URL`、修正 `apps/backend/data` 权限、按改动决定重建哪一端、启动、等待健康检查、打印已应用迁移与各阶段耗时。
|
||
|
||
### 按需重建(默认行为)
|
||
|
||
脚本把「上次成功部署的 commit + `.env` 指纹」记在项目根的 `.deploy-state`(已 gitignore),
|
||
再按 `git diff --name-only 上次commit..HEAD` 判断本次要重建哪一端:
|
||
|
||
| 改动 | 结果 |
|
||
| --- | --- |
|
||
| `apps/backend/**`(含 migrations) | 只重建 backend 镜像与容器 |
|
||
| `apps/frontend/**`、`deploy/caddy/**`、前端 Dockerfile | 只重建 web 镜像与容器 |
|
||
| `README.md`、`docs/**`、`deploy/*.sh`、`deploy/lib/**`、`scripts/**` | 不重建任何镜像,容器保持运行(只剩迁移 + 健康检查) |
|
||
| `docker-compose.yml`、`.env` 变化、未知路径、上次 commit 不在仓库里(force push/回滚) | 两端都重建(保守兜底) |
|
||
|
||
不需要重建的一侧既不跑 Docker 构建、也不 `--force-recreate`;如果它恰好没在运行(服务器重启过),
|
||
脚本用现有镜像把它拉起来。首次在有 `--skip-backup` 之前用本脚本时没有 `.deploy-state`,
|
||
会按"全量重建一次"处理,之后即进入增量。
|
||
|
||
备份策略同理:本次改动不含 `apps/backend/src/db/migrations/**` 时自动跳过迁移前备份
|
||
(生产上 `pg_dump` + 传 OSS 接近 12 分钟);`--backup` 可强制备份,`--skip-backup` 可无条件跳过,
|
||
只有"能算出改动范围"时才会自动跳过。
|
||
|
||
常用参数:
|
||
|
||
| 参数 | 说明 |
|
||
| --- | --- |
|
||
| `--dry-run` | 只打印计划后退出:上次部署的 commit、改动文件、要重建哪一端、是否备份、基础镜像 digest |
|
||
| `--all` | 强制两端都重建 |
|
||
| `--backend-only` / `--web-only` | 只重建指定一端 |
|
||
| `--backup` | 强制迁移前备份 |
|
||
| `--skip-backup` | 跳过迁移前备份 |
|
||
| `--reset-db` / `--yes` | 见下文「服务器重建数据库」 |
|
||
|
||
基础镜像也只补缺:`docker compose pull --ignore-buildable --policy missing`
|
||
(不再每次部署重新校验 postgres tag,避免顺手把数据库镜像静默升级)。
|
||
|
||
```bash
|
||
docker compose ps
|
||
docker compose logs -f backend
|
||
```
|
||
|
||
## 服务器日常更新
|
||
|
||
```bash
|
||
cd /home/order_site && git pull
|
||
bash deploy/ubuntu-deploy.sh # 想看这次会做什么:加 --dry-run
|
||
```
|
||
|
||
后端启动时自动执行未应用的增量迁移,一般不必单独跑迁移。
|
||
|
||
确认:
|
||
|
||
```bash
|
||
docker compose exec -T backend node --input-type=module -e '
|
||
const { query, closeDb } = await import("./dist/db/client.js");
|
||
try {
|
||
const result = await query("SELECT COALESCE(filename, name) AS name FROM schema_migrations ORDER BY COALESCE(filename, name)");
|
||
console.log(result.rows.map((row) => row.name).join("\n"));
|
||
} finally {
|
||
await closeDb();
|
||
}
|
||
'
|
||
```
|
||
|
||
## 服务器重建数据库
|
||
|
||
**会删除生产数据**。仅适合新环境、测试机或确认可丢数据的场景。
|
||
|
||
```bash
|
||
docker compose down
|
||
docker volume rm order_site_postgres_data
|
||
bash deploy/ubuntu-deploy.sh
|
||
```
|
||
|
||
或:
|
||
|
||
```bash
|
||
bash deploy/ubuntu-deploy.sh --reset-db
|
||
```
|
||
|
||
volume 名不确定时:
|
||
|
||
```bash
|
||
docker volume ls | grep postgres_data
|
||
```
|
||
|
||
不要随手:
|
||
|
||
```bash
|
||
docker compose down -v
|
||
```
|
||
|
||
这会连同 `caddy_data` / `caddy_config` 一起删掉,可能触发证书重签。
|
||
|
||
## 常见问题
|
||
|
||
### 后端一直未 ready
|
||
|
||
```bash
|
||
docker compose ps
|
||
docker compose logs --tail=120 backend
|
||
```
|
||
|
||
常见原因:
|
||
|
||
- `.env` 仍有 `change-me` 占位值
|
||
- `DATABASE_URL` 密码与已有 PostgreSQL volume 初始化密码不一致
|
||
- 容器部署里 `DATABASE_URL` 误写 `127.0.0.1`
|
||
- 某次增量迁移 SQL 失败
|
||
|
||
### 改了 `001_init.sql` 但旧库没变
|
||
|
||
已应用过的迁移不会因文件内容变更而重跑。正确做法是 **新增** `002_xxx.sql` 写增量。
|
||
|
||
仅本地可丢数据时,才用重建 volume 的方式对齐。
|
||
|
||
### 如何新增字段 / 索引
|
||
|
||
```bash
|
||
docker compose -f docker-compose.dev.yml exec -T backend \
|
||
npm run db:migrate:create -- add_orders_note
|
||
|
||
# 编辑生成的 apps/backend/src/db/migrations/00N_add_orders_note.sql
|
||
docker compose -f docker-compose.dev.yml exec -T backend npm run db:migrate
|
||
docker compose -f docker-compose.dev.yml exec -T backend npm run db:migrate:status
|
||
```
|
||
|
||
### 宿主机手动跑迁移连不上 `postgres`
|
||
|
||
`.env` 里的 `postgres` 只在 Docker 网络有效。宿主机请临时改为 `127.0.0.1` 并映射端口。
|