docs: 通用部署文档 — FRP + OpenResty + Docker 平台化部署模板

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

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-26 08:29:05 +08:00
parent 4ae8c69193
commit 8867e2f5fe
+520
View File
@@ -0,0 +1,520 @@
# 项目部署通用文档(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}` 替换为实际值,然后按第三章流程操作。