Files
qiji/通用部署文档.md
T
v6ole 8867e2f5fe docs: 通用部署文档 — FRP + OpenResty + Docker 平台化部署模板
包含:
- 完整端口规划表 (基础设施/项目后端/项目前端 三区间)
- 当前端口占用清单 (所有已用端口一目了然)
- 本机端口分配方案 (8000/8002/8004...)
- 新项目 7 步部署流程 (端口分配→FRP→后端→前端→OpenResty→DNS→验证)
- 前端 Dockerfile + nginx.conf 模板
- 日常运维速查 (FRP/容器/OpenResty/后端)
- 故障排查表
- 已有项目索引

特点:所有项目特定值均用 {PLACEHOLDER} 标记,复制即用

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-26 08:29:05 +08:00

521 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 项目部署通用文档(FRP + OpenResty + Docker 平台)
> **用途:** 本机开发 → 远程服务器(175.178.19.237) 的标准化部署方案。每个新项目复制此文档,填入具体端口和域名即可。
>
> **最后更新:** 2026-06-26
---
## 0. 端口规划与分配表
### 0.1 分配规则
| 资源 | 端口范围 | 数量 | 说明 |
|------|---------|------|------|
| 基础设施 | 1800018019 | 20 | frp / Casdoor / DB 隧道等 |
| 杂项服务 | 1802018049 | 30 | Sun-Panel / Bitwarden 等 |
| 项目后端 (frp) | 1806018079 | 20 | 每个项目占用 1 个 |
| 项目前端 (容器) | 1808018099 | 20 | 每个项目占用 1 个 |
| 预留 | 1810018199 | 100 | 未来扩展 |
> **新项目默认分配:** 后端 frp 取 18060–18079 中最小可用端口,前端容器取 18080–18099 中最小可用端口。
### 0.2 当前占用表
#### 基础设施 & 共享服务 (1800018019)
| 端口 | 资源 | 类型 | 说明 |
|------|------|------|------|
| 18000 | frps 控制 | 基础设施 | FRP 服务端控制端口 |
| 18001 | frps 管理面板 | 基础设施 | frps dashboard |
| 18002 | Halo | 其他 | 博客 |
| 18003 | Speedtest | 其他 | 网速测试 |
| 18004 | OpenList | 其他 | 播放列表 |
| 18005 | OpenList | 其他 | 播放列表 |
| 18006 | IP 监测 | 其他 | 公网 IP 变更监测 |
| 18009 | Gitea (frp) | 共享 | Git 仓库 |
| 18010 | Casdoor (frp) | 共享 | 统一认证 |
| 18011 | 通知服务 (frp) | 共享 | 消息推送 |
| 13306 | MySQL (frp) | 共享 | 数据库远程访问 |
| 15432 | PostgreSQL (frp) | 共享 | 数据库远程访问 |
| 16379 | Redis (frp) | 共享 | 缓存远程访问 |
#### 杂项服务 (1802018049)
| 端口 | 资源 | 说明 |
|------|------|------|
| 18020 | Sun-Panel | 导航面板 |
| 18051 | Bitwarden | 密码管理 |
| 18052 | CookieCloud | Cookie 同步 |
| 18054 | Bark Server | iOS 推送 |
| 18055 | WeChat API | 微信 API 服务 |
#### 项目后端 (frp 隧道, 1806018079)
| 远程端口 | 本机端口 | 项目 | 隧道名称 | 状态 |
|---------|---------|------|---------|------|
| 18060 | 8000 | H3ConuMS2 | h3conums-v2-backend | ✅ 运行中 |
| 18061 | 8002 | 企迹 (qiji) | qiji-backend | ⬜ 待部署 |
| 18062 | — | **可用** | — | — |
| 18063 | — | **可用** | — | — |
| 18064 | — | **可用** | — | — |
| 18065 | — | **可用** | — | — |
| 18066 | — | **可用** | — | — |
| 18067 | — | **可用** | — | — |
| 18068 | — | **可用** | — | — |
| 18069 | — | **可用** | — | — |
| 18070 | — | **可用** | — | — |
| 18071 | — | **可用** | — | — |
| 18072 | — | **可用** | — | — |
| 18073 | — | **可用** | — | — |
| 18074 | — | **可用** | — | — |
| 18075 | — | **可用** | — | — |
| 18076 | — | **可用** | — | — |
| 18077 | — | **可用** | — | — |
| 18078 | — | **可用** | — | — |
| 18079 | — | **可用** | — | — |
#### 项目前端 (Docker 容器, 1808018099)
| 远程端口 | 容器名 | 项目 | 状态 |
|---------|--------|------|------|
| 18062 | h3conums2-frontend | H3ConuMS2 (legacy) | ✅ 运行中 |
| 18063 | qiji-frontend | 企迹 (qiji) | ⬜ 待部署 |
| 18080 | — | **可用** | — |
| 18081 | — | **可用** | — |
| 18082 | — | **可用** | — |
| 18083 | — | **可用** | — |
| 18084 | — | **可用** | — |
| 18085 | — | **可用** | — |
| 18086 | — | **可用** | — |
| 18087 | — | **可用** | — |
| 18088 | — | **可用** | — |
| 18089 | — | **可用** | — |
| 18090 | — | **可用** | — |
| 18091 | — | **可用** | — |
| 18092 | — | **可用** | — |
| 18093 | — | **可用** | — |
| 18094 | — | **可用** | — |
| 18095 | — | **可用** | — |
| 18096 | — | **可用** | — |
| 18097 | — | **可用** | — |
| 18098 | — | **可用** | — |
| 18099 | — | **可用** | — |
> ⚠️ **注意:** H3ConuMS2 前端用了 18062(属于后端范围),是历史遗留。新项目严格按范围分配:后端 frp 取 1806018079,前端容器取 1808018099。
### 0.3 本机端口占用
| 本机端口 | 服务 | 项目 |
|---------|------|------|
| 3306 | MySQL | 共享 |
| 5432 | PostgreSQL | 共享 |
| 6379 | Redis | 共享 |
| 7000 | frpc 管理面板 | 基础设施 |
| 8000 | FastAPI 后端 | H3ConuMS2 |
| 8002 | FastAPI 后端 | 企迹 (qiji) |
| **8004** | — **可用** | — |
| **8006** | — **可用** | — |
| **8008** | — **可用** | — |
| 18000 | Casdoor | 共享 |
| 18001 | 通知服务 | 共享 |
| 18003 | Gitea | 共享 |
> **本机后端端口规划:** 新项目从 8004 开始,每次 +28004, 8006, 8008, ...),与 FRP 远程端口(18064, 18066, 18068, ...)保持对应关系,便于记忆。
---
## 1. 部署架构总览
```
用户浏览器 → {DOMAIN} → 远程服务器(175.178.19.237)
OpenResty (80/443)
┌──────────────┴──────────────┐
▼ ▼
前端容器 frp 隧道
{FRONTEND_CONTAINER} ({FRP_REMOTE_PORT})
(端口映射到宿主机) │
frps 服务端
(175.178.19.237)
frp 隧道
frpc 客户端
(本机服务器)
┌───────────┴───────────┐
▼ ▼
FastAPI 后端 Casdoor 认证
({BACKEND_PORT}) (18000, 共享)
PostgreSQL (5432, 共享)
```
---
## 2. 服务器信息
### 远程服务器(部署 frps + 前端容器 + OpenResty
| 项目 | 值 |
|------|-----|
| IP 地址 | 175.178.19.237 |
| SSH 端口 | 7072 |
| SSH 用户 | root |
| SSH 方式 | 本机已配置免密登录 |
| 系统 | Ubuntu 22.04 |
| 管理面板 | 1Panel (http://175.178.19.237:7071) |
| Web 服务器 | OpenResty (1panel/openresty) |
| FRP 服务端 | frps (snowdreamtech/frps:0.69.1, host 网络) |
| frps 配置路径 | `/opt/1panel/apps/frps/frps/data/frps.toml` |
| OpenResty 站点配置 | `/opt/1panel/apps/openresty/openresty/conf/conf.d/` |
### 本机服务器(部署 frpc + 后端 + 数据库)
| 项目 | 值 |
|------|-----|
| 角色 | 本地开发/服务机 |
| FRP 类型 | frpc 原生进程(非 Docker |
| frpc 配置文件 | `/etc/frp/frpc.toml` |
| frpc 管理 API | http://localhost:7000 (admin / 见实际配置) |
| frpc 进程 | `/usr/bin/frpc -c /etc/frp/frpc.toml` |
| Casdoor | localhost:18000(原生进程) |
| PostgreSQL | localhost:5432 |
---
## 3. 新项目部署流程
### 3.1 第一步:分配端口
从 [0.2 端口占用表](#02-当前占用表) 中选取最小可用端口,记录在此:
| 配置项 | 值 | 说明 |
|--------|-----|------|
| `PROJECT_NAME` | `_________` | 项目名称(英文短名,用于容器名和隧道名) |
| `DOMAIN` | `_________` | 域名(需配置 DNS |
| `BACKEND_PORT` | `_________` | 本机后端端口(如 8004) |
| `FRP_REMOTE_PORT` | `_________` | FRP 隧道远程端口(从 1806018079 选) |
| `FRONTEND_CONTAINER_PORT` | `_________` | 前端容器宿主机端口(从 18080–18099 选) |
| `FRP_TUNNEL_NAME` | `_________` | FRP 隧道名称(如 `xxx-backend` |
| `CONTAINER_NAME` | `_________` | 前端容器名(如 `xxx-frontend` |
> 分配后请更新本文档的 [0.2 端口占用表](#02-当前占用表),标记为已占用。
### 3.2 第二步:添加 FRP 隧道
编辑 `/etc/frp/frpc.toml`,在末尾添加:
```toml
[[proxies]]
name = "{FRP_TUNNEL_NAME}"
type = "tcp"
localIP = "127.0.0.1"
localPort = {BACKEND_PORT}
remotePort = {FRP_REMOTE_PORT}
transport.useEncryption = true
```
重启 frpc
```bash
# frpc 是原生进程,kill 后自动重启(由 entrypoint 脚本守护)
sudo kill $(pgrep frpc) && sudo /usr/local/bin/entrypoint.d/20-frpc-start.sh &
```
验证隧道:
```bash
curl -s http://localhost:7000/api/status -u admin:{FRP_PASSWORD} | python3 -m json.tool | grep {FRP_TUNNEL_NAME}
# 应显示 "status": "running"
```
### 3.3 第三步:配置后端
确保后端 `.env` 中:
```env
CORS_ORIGINS=["http://localhost:5173","https://{DOMAIN}","http://{DOMAIN}"]
CASDOOR_ENDPOINT=http://localhost:18000
```
启动后端:
```bash
cd /home/v6ole/pyproject/{PROJECT_NAME}/backend
uvicorn app.main:app --host 0.0.0.0 --port {BACKEND_PORT} &
```
### 3.4 第四步:构建并部署前端
前端需要有 `Dockerfile`(多阶段构建:node 构建 + nginx 运行)和 `nginx.conf`
```bash
# 1. 构建镜像
cd /home/v6ole/pyproject/{PROJECT_NAME}/frontend
docker build -t {CONTAINER_NAME}:latest .
# 2. 推送到远程
docker save {CONTAINER_NAME}:latest | ssh -p 7072 root@175.178.19.237 "docker load"
# 3. 启动容器
ssh -p 7072 root@175.178.19.237 "
docker stop {CONTAINER_NAME} 2>/dev/null
docker rm {CONTAINER_NAME} 2>/dev/null
docker run -d \
--name {CONTAINER_NAME} \
--restart always \
-p {FRONTEND_CONTAINER_PORT}:80 \
{CONTAINER_NAME}:latest
"
# 4. 验证
curl -s http://175.178.19.237:{FRONTEND_CONTAINER_PORT}/ | head -5
```
### 3.5 第五步:配置 OpenResty
在远程创建 `/opt/1panel/apps/openresty/openresty/conf/conf.d/{DOMAIN}.conf`
```nginx
server {
listen 80;
listen 443 ssl http2;
server_name {DOMAIN};
index index.html;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
access_log /www/sites/{DOMAIN}/log/access.log main;
error_log /www/sites/{DOMAIN}/log/error.log;
# ACME 验证
location ^~ /.well-known/acme-challenge {
allow all;
root /usr/share/nginx/html;
}
# 安全头
add_header Strict-Transport-Security "max-age=31536000";
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 前端
location / {
proxy_pass http://127.0.0.1:{FRONTEND_CONTAINER_PORT};
}
# 后端 API
location /api/ {
proxy_pass http://127.0.0.1:{FRP_REMOTE_PORT};
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 60s;
}
# HTTP → HTTPS
if ($scheme = http) {
return 301 https://$host$request_uri;
}
# SSL(通过 1Panel 申请 Let's Encrypt
ssl_certificate /www/sites/{DOMAIN}/ssl/fullchain.pem;
ssl_certificate_key /www/sites/{DOMAIN}/ssl/privkey.pem;
ssl_protocols TLSv1.3 TLSv1.2;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
error_page 497 https://$host$request_uri;
proxy_set_header X-Forwarded-Proto https;
}
```
应用配置:
```bash
# 创建日志和 SSL 目录
ssh -p 7072 root@175.178.19.237 "mkdir -p /www/sites/{DOMAIN}/log /www/sites/{DOMAIN}/ssl"
# 上传配置文件
scp -P 7072 /path/to/{DOMAIN}.conf root@175.178.19.237:/opt/1panel/apps/openresty/openresty/conf/conf.d/
# 重载 OpenResty(在 1Panel 面板操作,或命令行)
ssh -p 7072 root@175.178.19.237 "docker restart openresty"
```
### 3.6 第六步:配置 DNS + SSL
1. **DNS** 在域名管理后台添加 A 记录 `{DOMAIN}``175.178.19.237`
2. **SSL** 通过 1Panel 面板为 `{DOMAIN}` 申请 Let's Encrypt 证书
3. **Casdoor** 在 Casdoor 应用中添加回调地址 `https://{DOMAIN}/callback`
### 3.7 第七步:验证
按顺序检查:
- [ ] 后端进程:`ps aux | grep uvicorn | grep {BACKEND_PORT}`
- [ ] 本机 API`curl http://localhost:{BACKEND_PORT}/api/health`
- [ ] FRP 隧道:管理 API 中状态为 `running`
- [ ] 远程 API`ssh -p 7072 root@175.178.19.237 "curl -s http://127.0.0.1:{FRP_REMOTE_PORT}/api/health"`
- [ ] 前端容器:`ssh -p 7072 root@175.178.19.237 "docker ps | grep {CONTAINER_NAME}"`
- [ ] 前端页面:`curl -s http://175.178.19.237:{FRONTEND_CONTAINER_PORT}/ | head -3`
- [ ] OpenResty`curl -s https://{DOMAIN}/api/health`
- [ ] 完整流程:浏览器访问 `https://{DOMAIN}` → 登录 → 功能验证
---
## 4. 前端 Dockerfile 模板
```dockerfile
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```
> **nginx.conf 说明:** 容器内 nginx 只需处理静态文件 SPA fallback。API 代理由边缘 OpenResty 负责,容器内不需要 `/api/` 代理配置。如需容器独立运行(非本平台),保留 `/api/` 代理指向 `backend` 容器名。
### 极简 nginx.conf(平台部署用)
```nginx
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# 可选:健康检查
location /health {
return 200 "ok";
}
}
```
---
## 5. 日常运维速查
### 5.1 FRP 管理
```bash
# 查看所有隧道状态
curl -s http://localhost:7000/api/status -u admin:{FRP_PASSWORD} | python3 -c "
import sys,json
d=json.load(sys.stdin)
for p in d.get('tcp',[]):
print(f\"{p['name']:30s} {p['status']:8s} {p['local_addr']:20s} → {p['remote_addr']}\")
"
# 重启 frpc
sudo kill $(pgrep frpc) && sudo /usr/local/bin/entrypoint.d/20-frpc-start.sh &
# 远程 frps 日志
ssh -p 7072 root@175.178.19.237 "docker logs --tail 20 Frps"
```
### 5.2 前端容器管理
```bash
# 列出所有前端容器
ssh -p 7072 root@175.178.19.237 "docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}'"
# 更新前端(构建→推送→重启)
cd /home/v6ole/pyproject/{PROJECT_NAME}/frontend
docker build -t {CONTAINER_NAME}:latest . && \
docker save {CONTAINER_NAME}:latest | ssh -p 7072 root@175.178.19.237 \
"docker load && docker stop {CONTAINER_NAME} && docker rm {CONTAINER_NAME} && docker run -d --name {CONTAINER_NAME} --restart always -p {FRONTEND_CONTAINER_PORT}:80 {CONTAINER_NAME}:latest"
# 查看日志
ssh -p 7072 root@175.178.19.237 "docker logs --tail 50 {CONTAINER_NAME}"
```
### 5.3 OpenResty 管理
```bash
# 检查配置语法
ssh -p 7072 root@175.178.19.237 "docker exec openresty nginx -t"
# 重载配置(推荐通过 1Panel 面板操作)
ssh -p 7072 root@175.178.19.237 "docker restart openresty"
# 查看访问日志
ssh -p 7072 root@175.178.19.237 "tail -50 /www/sites/{DOMAIN}/log/access.log"
```
### 5.4 后端管理
```bash
# 查看所有后端进程
ps aux | grep uvicorn | grep -v grep
# 重启后端
pkill -f "uvicorn app.main:app.*{BACKEND_PORT}"
cd /home/v6ole/pyproject/{PROJECT_NAME}/backend && uvicorn app.main:app --host 0.0.0.0 --port {BACKEND_PORT} &
```
---
## 6. 故障排查
| 现象 | 检查项 |
|------|--------|
| 页面 502/504 | OpenResty 代理目标可达?前端容器运行?FRP 隧道正常? |
| API 无响应 | `curl localhost:{BACKEND_PORT}/api/health` 本机是否通?FRP 隧道状态? |
| 登录/回调失败 | Casdoor 回调地址是否包含 `https://{DOMAIN}/callback`?前端环境变量 Casdoor 地址是否正确? |
| 静态资源 404 | 前端构建是否成功?nginx try_files 配置是否正确? |
| FRP 隧道 offline | frpc 进程是否存活?远程端口是否被占用?网络是否可达? |
| SSL 证书错误 | 证书是否过期?1Panel 自动续期是否正常? |
| CORS 报错 | 后端 `CORS_ORIGINS` 是否包含生产域名? |
---
## 7. 已有项目索引
| 项目 | 域名 | 后端(本机) | FRP(远程) | 前端(远程) | 文档 |
|------|------|-----------|----------|-----------|------|
| H3ConuMS2 | onu.dhdx.fun | 8000 | 18060 | 18062 | H3ConuMS2部署交接文档.md |
| 企迹 (qiji) | qj.dhdx.fun | 8002 | 18061 | 18063 | qiji部署交接文档.md |
---
## 8. 关键注意事项
1. **frpc 是原生进程**:修改 `/etc/frp/frpc.toml` 后需手动 kill + 重启,不能用 `systemctl`
2. **frps 是 Docker 容器**:远程 frps 配置在容器内,重启容器即生效
3. **OpenResty 由 1Panel 管理**:直接改配置文件后需在面板中重载,或重启 openresty 容器
4. **端口不可冲突**:分配新端口前务必检查 [0.2 端口占用表](#02-当前占用表)
5. **分配后更新本文档**:每次新增项目,更新端口占用表和项目索引
6. **Casdoor 共享**:所有项目共用同一个 Casdoor 实例(本机 18000
7. **数据库隔离**:不同项目使用同一 PostgreSQL 实例的不同 database
8. **frp 认证 token** 在 frpc 和 frps 两端必须一致
9. **所有 frp 隧道已启用加密传输**`transport.useEncryption = true`
10. **MinIO** 在内网 `10.10.10.13:17051`,公网项目如需文件上传/查看,需要额外配置 frp 隧道或后端代理
---
> **模板使用说明:** 新项目部署时复制此文档,将 `{PROJECT_NAME}`、`{DOMAIN}`、`{BACKEND_PORT}`、`{FRP_REMOTE_PORT}`、`{FRONTEND_CONTAINER_PORT}`、`{FRP_TUNNEL_NAME}`、`{CONTAINER_NAME}` 替换为实际值,然后按第三章流程操作。