P5-prep Task 1(docs/plans/2026-09-21-P5-准备件.md): - docker/server.Dockerfile:单镜像 photo-live-server 多角色(api-cn/api-ov/ worker/sync/migrate-deploy/seed-admin,entrypoint.sh 分发;migrate-deploy 支持 cn|ov|all 区域参数)。三阶段:全量 install(锁文件冻结)→ prisma generate 双库 + 逐包构建;--prod 依赖层;runtime=node:20-alpine+openssl/curl + 全局 prisma@5.22.0(对齐锁文件)+ 双库 client 镜像内重建(db-cn 落 pnpm store .prisma、db-ov 落包内 generated/)。sharp 走 musl 预编译验证通过。 - docker/web.Dockerfile + infra/deploy/nginx.conf.template:photo-live-web (nginx:alpine + envsubst 模板)。viewer(/,history 回退) + admin(/admin/, hash 路由) + /v1 反代 API_UPSTREAM(含 /v1/realtime WS 升级)+ /assets/、/admin/assets/、/embed/v1/ immutable 长缓存(admin assets 的 alias 前缀剥除坑已实测修复)。VITE_* 为构建期参数(VIEWER_BASE/ EMBED_ORIGIN 随域名构建传入)。 - infra/deploy/docker-compose.cn.yml / -ov.yml:cn=api-cn+worker+sync+web, ov=api-ov+web-ov(核实架构:sync 仅 cn 侧,inbox/emergency 内嵌 api-ov, 无 ov 侧 sync 进程);健康检查 /v1/health、unless-stopped、json-file 10m×3、外部 1panel-network 复用 1Panel MySQL/Redis、SERVER_IMAGE/ WEB_IMAGE 环境变量化镜像 tag(回滚杠杆)。 - .env.production.example-cn/-ov:生产变量全量清单(cn 25 键/ov 24 键, DB/Redis/OSS/SYNC/AUTH/限速/域名分组注释,源自 services+packages process.env 全量审计);.gitignore 放行 example,真实 .env.production-* 永不入库。 - infra/deploy/README-1Panel.md:镜像导入双路径(save tar 导入 / 仓库 pull)、 MySQL 建库授权 SQL(utf8mb4_0900_bin,<8.0.30 回退 utf8mb4_bin——D12)、 Redis 连接、1Panel 编排、OpenResty 反代+证书、migrate-deploy 一次性容器、 首次部署顺序(DB→迁移→ov 栈→cn 栈→seed→冒烟)。 - 根 package.json pnpm.onlyBuiltDependencies 增补 sharp/prisma/@prisma/ engines/@prisma/client(镜像构建所需 install 脚本白名单)。 本地验证:两镜像构建成功(server 988MB / web 97.7MB);compose config 校验 通过;server 镜像内逐服务 require(sharp/argon2/双 Prisma client/bullmq/ ioredis)与双引擎 so.node、migrate-deploy 角色、无 .env 泄漏均实测;web 镜像 容器实测路由(viewer 回退/admin 子路径/assets 与 embed immutable/WS 代理路径 502 语义)全通过。未起真实栈(无真实 DB,符合准备件边界)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
8.3 KiB
README-1Panel —— photo-live 生产部署指南(1Panel 单机双地域)
P5-prep 产物(无凭证前置批):本指南只描述操作步骤与占位值,不含任何真实密钥。 目标形态:cn 服务器(1Panel:MySQL/Redis/OpenResty + photo-live-cn 编排) 与 ov 服务器(1Panel:MySQL/Redis/OpenResty + photo-live-ov 编排)。 同步架构(已从代码核实):sync 独立进程只跑 cn 侧,双库直连 + 双 API 调用; ov 侧 inbox(内容接收)与 emergency(紧急停用)内嵌在 api-ov 进程内。
| 产物 | 说明 |
|---|---|
docker/server.Dockerfile |
单镜像 photo-live-server 多角色:api-cn / api-ov / worker / sync / migrate-deploy / seed-admin |
docker/web.Dockerfile |
photo-live-web:nginx 承载 viewer(/)+admin(/admin/)+/v1 反代+静态长缓存 |
docker-compose.cn.yml / docker-compose-ov.yml |
两地域编排(本目录) |
.env.production.example-cn / -ov |
变量全量清单(仓库根,复制后填值) |
RUNBOOK.md |
发布/迁移/回滚/紧急停用运行手册 |
1. 镜像导入(两条路径任选)
版本约定:tag = git短sha-日期(如 12059d9-20260921),同一 tag 双镜像配套,
回滚即换 tag(见 RUNBOOK.md)。
路径 A:本地构建 → tar 上传 → 1Panel 导入(无镜像仓库时)
# 本地(仓库根)构建;tag 按上方约定替换
docker build -f docker/server.Dockerfile -t photo-live-server:12059d9-20260921 .
docker build -f docker/web.Dockerfile \
--build-arg VITE_VIEWER_BASE=https://你的viewer域名 \
--build-arg VITE_EMBED_ORIGIN=https://你的viewer域名 \
-t photo-live-web:12059d9-20260921 .
docker save photo-live-server:12059d9-20260921 | gzip > photo-live-server.tar.gz
docker save photo-live-web:12059d9-20260921 | gzip > photo-live-web.tar.gz
# 上传两台服务器后,1Panel → 容器 → 镜像 → 导入(选择 tar.gz)
web 镜像的
VITE_VIEWER_BASE/VITE_EMBED_ORIGIN是构建期变量(admin 分享卡 与嵌入代码拼 URL 用);cn/ov 两地域各用各自域名构建一次。漏传只影响分享卡外链, 不影响 API 路由(API 基址=同源 /v1,由运行时API_UPSTREAM决定)。
路径 B:推镜像仓库 → 服务器 pull(有仓库时)
1Panel → 容器 → 仓库 → 创建(阿里云 ACR 个人版即可)→ 之后编排文件里 image 直接写
registry.cn-hangzhou.aliyuncs.com/你的命名空间/photo-live-server:tag,或在服务器
docker pull 后 docker tag 成编排里用的名字。
2. MySQL 建库 / 授权(两台各执行对应段)
1Panel → 数据库 → MySQL(8.0+)→ 创建数据库。库名/字符集:
-- cn 服务器(库名 photo_cn)
CREATE DATABASE photo_cn CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin;
CREATE USER 'photo'@'%' IDENTIFIED BY '强密码';
GRANT ALL PRIVILEGES ON photo_cn.* TO 'photo'@'%';
FLUSH PRIVILEGES;
-- ov 服务器(库名 photo_ov)
CREATE DATABASE photo_ov CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin;
CREATE USER 'photo_ov'@'%' IDENTIFIED BY '强密码';
GRANT ALL PRIVILEGES ON photo_ov.* TO 'photo_ov'@'%';
FLUSH PRIVILEGES;
utf8mb4_0900_bin(大小写敏感)是账号/token/publicToken 语义的前提,需 MySQL ≥ 8.0.30(决策 D12:先在两台 ECS 上SELECT VERSION();登记精确版本; 若低于 8.0.30,改用utf8mb4_bin(同样大小写敏感),并重跑语义相关验收)。- 授权 host:
'%'便于容器网段接入;生产更稳妥做法是限定网段 ('172.%'容器网 + cn 服务器出口 IP)。ov 库必须额外放行 cn 服务器出口 IP 的 3306(sync 进程直连 ov 库),建议叠加 SSL 或隧道(P5 实测定案)。
3. Redis 连接
1Panel → 数据库 → Redis → 创建(默认库即可)。连接串二选一:
- 容器网络互连(推荐):photo-live 编排与 1Panel 的 Redis 同在
1panel-network,host 直接写 Redis 容器名(docker ps查名,如1Panel-redis-xxxx),端口写容器内端口 6379。 - 宿主机 IP 直连:host 写宿主机内网 IP;1Panel 应用端口默认只绑 127.0.0.1, 需在 1Panel 端口设置放开监听地址或改绑 0.0.0.0(注意防火墙)。
填入 env:cn REDIS_URL_CN;ov REDIS_URL_OV。
4. Compose 编排(1Panel → 容器 → 编排 → 创建编排)
- 文件管理器在 1Panel 数据目录旁新建目录(示例
/opt/photo-live/),上传:docker-compose.cn.yml(cn)/docker-compose-ov.yml(ov)- 由仓库根
.env.production.example-cn/-ov复制改名的.env.production-cn/.env.production-ov(与 compose 同目录,已填值)
- 1Panel → 容器 → 编排 → 创建编排 → 路径选择该目录(或直接粘贴 compose 内容, 再把 env 文件放到 1Panel 显示的编排目录内)。
- 默认编排挂外部网络
1panel-network(1Panel 应用网络,容器名互通 MySQL/ Redis)。若你的 1Panel 网络名不同(docker network ls核对),改 compose 末段name:;若用宿主机 IP 连接,把 networks 整段换成photo-net: {}并在 env 里 用 IP 形态的连接串。 - 启动前确认镜像 tag:默认
photo-live-server:latest/photo-live-web:latest; 建议在 1Panel 编排的环境变量里固定SERVER_IMAGE/WEB_IMAGE为具体版本 tag。
5. OpenResty 网站 + 证书反代到 web 容器
1Panel → 网站 → 创建网站 → 反向代理:
- cn:主域名
photo.example.cn(viewer)→ 代理http://127.0.0.1:8088; 管理端admin.photo.example.cn→ 同一后端http://127.0.0.1:8088(admin 走 hash 路由/admin/路径,也可与 viewer 同域直接用photo.example.cn/admin/)。 - ov:
photo.example.com→http://127.0.0.1:8089。 - 证书:网站设置 → HTTPS → 申请/绑定证书,开启「强制 HTTPS」。
- OpenResty 侧注意:
- 不要重复缓存
/v1/(API 动态;图片 URL 本身是带签名的 OSS 直链,不经过 这层);静态资源缓存已在 photo-live-web 容器内配置好。 - WebSocket(
/v1/realtime)透传:1Panel 反代默认透传 Upgrade 头;若自建 OpenResty 配置,需保留proxy_set_header Upgrade/Connection(参考nginx.conf.template的 /v1/ 段)。 - 若 web 端口改绑了非回环地址,注意防火墙只放行 OpenResty 出口。
- 不要重复缓存
6. 迁移执行(migrate-deploy 角色,一次性容器)
# cn 服务器:迁移 cn 库(env 文件与 compose 同目录;--rm 用完即删)
docker run --rm --env-file /opt/photo-live/.env.production-cn \
--network 1panel-network \
photo-live-server:12059d9-20260921 migrate-deploy cn
# ov 服务器:迁移 ov 库
docker run --rm --env-file /opt/photo-live/.env.production-ov \
--network 1panel-network \
photo-live-server:12059d9-20260921 migrate-deploy ov
# 首次引导 admin 账号(cn;幂等,已存在则跳过)
docker run --rm --env-file /opt/photo-live/.env.production-cn \
--network 1panel-network \
photo-live-server:12059d9-20260921 seed-admin
migrate-deploy 支持区域参数 cn | ov | all(缺省 all——单机双库演练用;生产两台
各跑各的)。Prisma migrate deploy 只向前应用未执行过的迁移,已在
RUNBOOK.md 固化为发布步骤与回滚原则。
7. 首次部署顺序(cn 与 ov 并行各自的 1-2,汇合于 3-6)
- DB 就绪:两台各建库/授权(§2)+ Redis(§3)。
- 镜像导入:两台各自导入 server+web 镜像(§1)。
- 迁移:cn
migrate-deploy cn→ ovmigrate-deploy ov(§6)。 - 起栈:ov 先
docker-compose-ov.ymlup(api-ov + web-ov)→ cndocker-compose.cn.ymlup(api-cn + worker + sync + web)。sync 依赖 ov API 与 ov 库可达,故 cn 栈最后起。 - 域名/证书:OpenResty 反代 + HTTPS(§5),DNS 解析两地域各自接入。
- 引导与演练:cn
seed-admin(§6)→ 管理端登录 → 建测试相册 →scripts/prod-smoke.sh <cnBase> <ovBase> <账号> <密码>四点冒烟 (健康/登录/上传/公开读,ov 未部署时--ov跳过 ov 段)→ 观察同步水位 (sync 日志outbox batch pushed)→ 清理测试数据(软删照片即可)。
日常发布/回滚见 RUNBOOK.md。