Files
FastapiAdmin/DEPLOYMENT.md
T
zhangtao c73daa7eae chore(deploy): 健康检查发正确 Host、list 型配置改 JSON、Host 校验收窄
- backend 健康检查改为发送正确的 Host 头(而非放宽 ALLOWED_HOSTS);
  配合 ALLOW_LOCALHOST_HOSTS=false,Host: localhost 现在返回 400,容器自检仍 exit 0
- OAUTH_ALLOWED_HOSTS / CORS_STRICT_STARTUP / SCHEDULER_ALLOW_CODE_EXEC 接入 compose 注入
- ⚠️ OAUTH_ALLOWED_HOSTS 是 list[str],必须写 JSON 数组:本次部署曾因写成逗号分隔字符串
  导致启动期 SettingsError、容器重启、网关 502(已修复,并在 compose 默认值中固化 JSON 形态)
- DEPLOYMENT.md 新增「配置取值格式」章节记录该坑与排查方法
- AUDIT-REPORT.md 更新修复进展(含复核 t12 驳回与 t13/t14 通过、残余项)

线上验证:四端 200;Host=localhost → 400;健康检查 exit 0;公网 docs/.DS_Store → 403
2026-10-04 05:12:37 +08:00

6.8 KiB
Raw Blame History

FastapiAdmin 部署运维手册

适用环境:阿里云单机 Docker 部署(service.fastapiadmin.com) 最近更新:2026-10-04(本轮密钥轮换与加固之后)


1. 架构与目录

组件 说明 对外端口
nginx TLS 终止、静态站点、反向代理 /api/v1 80 / 443
backend FastAPI(uvicorn,1 worker,非 root 运行 uid=1000) 仅 127.0.0.1:8001
mysql MySQL 8.0,数据卷 mysql_data 仅 127.0.0.1:3306
redis Redis 7(会话/缓存/限流) 仅 127.0.0.1:6379

服务器布局(/home/FastapiAdmin):

docker/                     # 唯一部署目录,不含业务源码
├── docker-compose.yaml     # 服务编排(端口仅 127.0.0.1,密钥由 .env 注入)
├── docker-compose.override.yaml  # nginx 版本固定 + 缓存配置挂载 + ACME 目录
├── .env                    # 真实凭据(600,不入库)
├── .env.example            # 占位模板(入库)
├── backend/Dockerfile      # 源码构建路径(正式部署不用它)
├── nginx/
│   ├── nginx.conf          # 站点配置(含文档封堵、隐藏文件拒绝、ACME 放行)
│   ├── cache-headers.inc   # 静态资源长缓存片段
│   ├── acme/               # ACME http-01 校验目录
│   ├── ssl/                # 证书与私钥(600,不入库)
│   └── {web,app,docs}/dist # 三端静态成品(由本地构建后上传)
└── mysql/ redis/           # 初始化 SQL 与 redis.conf
backups/                    # 部署前自动备份(每类保留 5 份)

仓库侧:deploy-artifacts.sh(本机执行的成品部署脚本)、backend/requirements/(依赖分组)、.github/workflows/(CI)。


2. 日常部署

在本机仓库根目录执行(需要本机可免密 SSH 到服务器、且本机 Docker 可用——colima start):

./deploy-artifacts.sh web        # 构建并部署 Web 管理后台
./deploy-artifacts.sh app        # 构建并部署移动端 H5
./deploy-artifacts.sh backend    # 本地构建 MySQL 版镜像 → 传输 → 只重建 backend
./deploy-artifacts.sh all        # 三样全发
./deploy-artifacts.sh verify     # 公网四端验证
./deploy-artifacts.sh backups    # 列出服务器备份
./deploy-artifacts.sh prune      # 轮转清理旧备份与旧镜像
./deploy-artifacts.sh rollback web|app|backend

要点:

  • 服务器不接收源码、不执行构建:后端以 docker save → ssh → docker load 交付镜像,前端以 tar 管道交付成品。
  • 每次部署前自动备份到 backups/<标签>-<时间戳>,备份失败即中止,不会带着假备份上线。
  • 只重建变化的容器;MySQL/Redis/Nginx 不受影响。
  • PostgreSQL 版本镜像:./deploy-artifacts.sh backend-pg(只构建与自检,不切换线上)。切换需人工把 BACKEND_IMAGE_TAG 指到该镜像并确保 compose 的数据库配置已改。

3. 密钥与口令

项 位置 说明
SECRET_KEY 服务器 docker/.env JWT 签名密钥,64 位 hex,随机生成
DATA_ENCRYPTION_KEY 服务器 docker/.env 落库敏感数据加密主密钥
DATA_ENCRYPTION_OLD_KEYS 服务器 docker/.env 轮换前的旧密钥,仅用于解密历史密文
种子账号口令 服务器 /root/.fa-seed-passwords(600) super/admin/user,登录后请自行修改并删除该文件

轮换密钥的正确步骤(不可跳步,否则历史密文无法解密):

  1. 把当前 DATA_ENCRYPTION_KEY(未设置时取当前 SECRET_KEY)追加到 DATA_ENCRYPTION_OLD_KEYS;
  2. 生成新值:openssl rand -hex 32,写入 SECRET_KEY / DATA_ENCRYPTION_KEY;
  3. docker compose up -d --force-recreate backend;
  4. 验证历史密文仍可解密(例如存储源口令),再删除 OLD_KEYS 中不再需要的旧值。

注意:更换 SECRET_KEY 会使已签发的令牌全部失效,用户需重新登录。


4. 运行状态与巡检

  • 每日巡检:systemctl list-timers fa-health-check.timer(每天 08:30,带 5 分钟随机延迟)
  • 巡检内容:4 个容器健康、/api/v1/monitor/health/check 与 /web/、/app/ 可用性、证书剩余天数、磁盘、swap、备份与镜像堆积
  • 结果:/var/log/fa-health.log;有告警时写入 syslog(journalctl -t fa-health)且服务退出码非 0
  • 手动执行:systemctl start fa-health-check.service && tail -n 30 /var/log/fa-health.log

常用排查命令:

cd /home/FastapiAdmin/docker
docker compose ps
docker logs --tail 100 backend
docker inspect backend --format '{{.State.Health.Status}}'
ss -tlnp | grep docker-proxy      # 确认对外只有 80/443

5. 证书

  • 当前证书:docker/nginx/ssl/server.pem(CN=service.fastapiadmin.com),由巡检脚本监控到期天数
  • 续期(重新签发后):替换 server.pem 与 server.key → docker exec nginx nginx -s reload
  • 已放行 ACME http-01 校验路径(/.well-known/acme-challenge/ → docker/nginx/acme),如需切换到 acme.sh 自动续期,把校验文件写入该目录即可,无需再改 nginx 配置

6. 配置取值格式(踩过的坑)

.env / compose 里的取值格式取决于应用侧字段类型:

类型 写法 例子
str(应用内部再按逗号拆) 逗号分隔字符串 PROD_CORS_ORIGINS=https://a.com,https://b.com
list[str](pydantic-settings 直接解析) 必须 JSON 数组字面量 OAUTH_ALLOWED_HOSTS=["service.fastapiadmin.com","*.fastapiadmin.com"]

把 list[str] 字段写成逗号分隔字符串会让应用启动期抛 SettingsError,容器不断重启、网关返回 502。2026-10-04 的部署事故就是这个原因(已修复并在 compose 默认值里固化 JSON 形态)。

排查方式:docker logs --tail 40 backend,若看到 pydantic_settings.exceptions.SettingsError: error parsing value for field "XXX" from source "EnvSettingsSource", 就是该字段的取值格式与类型不匹配。

7. 已知遗留问题

安全与质量债的完整清单见 AUDIT-REPORT.md 与各分报告(backend/audit-*.md、frontend/audit-*.md、docker/audit-deploy.md)。当前仍待处理的高优先级项:

  1. 会话失效机制:停用/删除/改密后旧会话仍有效(最长 7 天)——需应用侧改造;
  2. 数据权限注入不完整:CRUDBase 的删除/清空/改状态路径未注入数据权限;
  3. Web i18n 与通知状态枚举:85/101 个页面未接入 i18n;通知状态枚举与后端语义冲突;
  4. 测试覆盖:后端仅 2 个用例,夹具掩盖缺陷(见 backend/audit-testing.md);
  5. 多租户需求与实现错位(见 backend/audit-requirements.md)。