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

18 KiB
Raw Blame History

项目部署通用文档(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 端口占用表 中选取最小可用端口,记录在此:

配置项 说明
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 端口占用表,标记为已占用。

3.2 第二步:添加 FRP 隧道

编辑 /etc/frp/frpc.toml,在末尾添加:

[[proxies]]
name = "{FRP_TUNNEL_NAME}"
type = "tcp"
localIP = "127.0.0.1"
localPort = {BACKEND_PORT}
remotePort = {FRP_REMOTE_PORT}
transport.useEncryption = true

重启 frpc

# frpc 是原生进程,kill 后自动重启(由 entrypoint 脚本守护)
sudo kill $(pgrep frpc) && sudo /usr/local/bin/entrypoint.d/20-frpc-start.sh &

验证隧道:

curl -s http://localhost:7000/api/status -u admin:{FRP_PASSWORD} | python3 -m json.tool | grep {FRP_TUNNEL_NAME}
# 应显示 "status": "running"

3.3 第三步:配置后端

确保后端 .env 中:

CORS_ORIGINS=["http://localhost:5173","https://{DOMAIN}","http://{DOMAIN}"]
CASDOOR_ENDPOINT=http://localhost:18000

启动后端:

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

# 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

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;
}

应用配置:

# 创建日志和 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}
  • 本机 APIcurl http://localhost:{BACKEND_PORT}/api/health
  • FRP 隧道:管理 API 中状态为 running
  • 远程 APIssh -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
  • OpenRestycurl -s https://{DOMAIN}/api/health
  • 完整流程:浏览器访问 https://{DOMAIN} → 登录 → 功能验证

4. 前端 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(平台部署用)

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 管理

# 查看所有隧道状态
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 前端容器管理

# 列出所有前端容器
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 管理

# 检查配置语法
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 后端管理

# 查看所有后端进程
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 端口占用表
  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} 替换为实际值,然后按第三章流程操作。