178 lines
11 KiB
Markdown
178 lines
11 KiB
Markdown
# 部署指南(Docker Compose)
|
||
|
||
> 本地开发请用 [`./scripts/start.sh`](../quickstart.md),不要用本文流程。
|
||
> 本文所有命令都在**仓库根目录**执行;部署脚本已迁到 `scripts/`,写法固定为 `./scripts/deploy.sh …`。
|
||
|
||
## 1. 运行拓扑
|
||
|
||
`docker-compose.yml` 定义 4 个服务,容器名前缀 `nex-docus-`,基础镜像走华为云 SWR 镜像站(国内直连可用):
|
||
|
||
| 服务 | 容器 | 端口(宿主机→容器) | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `mysql` | `nex-docus-mysql` | `${MYSQL_PORT:-3306}` → 3306 | MySQL 8.0,`utf8mb4` / `utf8mb4_unicode_ci`,数据在 `${STORAGE_PATH}/mysql` |
|
||
| `redis` | `nex-docus-redis` | `${REDIS_PORT:-6379}` → 6379 | Redis 7,`requirepass` + AOF,数据在 `${STORAGE_PATH}/redis` |
|
||
| `backend` | `nex-docus-backend` | `${BACKEND_PORT:-8000}` → 8000 | 由 `backend/Dockerfile` 构建;启动命令 `python scripts/init_db.py && uvicorn main:app …`,即**建表/迁移/种子全自动** |
|
||
| `frontend` | `nex-docus-frontend` | `${FRONTEND_PORT:-8080}` → 80 | 由 `frontend/Dockerfile` 构建(node 构建 + nginx 托管),nginx 反代 `/api/`、`/mcp` 到后端 |
|
||
|
||
- 网络:`nex-docus-network`;后端在容器内的上游主机名是 `backend`,前端 nginx 依赖该名字。
|
||
- **文件存储**:宿主机 `${STORAGE_PATH}` 挂到后端 `/data/nex_docus_store`(后端配置里的 `STORAGE_ROOT`)。文档正文、向量索引、搜索索引、PDF 缓存都在这里。
|
||
- ⚠️ compose 里的相对路径按**执行 compose 的当前目录**解析,建议 `.env` 中把 `STORAGE_PATH` 写成绝对路径(如 `/data/nexdocus/storage`),避免换目录执行时数据"凭空消失"。
|
||
|
||
## 2. 前置要求
|
||
|
||
- Docker 20.10+,Docker Compose v2(`docker compose`;脚本也兼容老 `docker-compose`)
|
||
- 磁盘 ≥ 15 GB 空闲(后端镜像要装 PyTorch / weasyprint / zvec,镜像本身 + 向量索引 + 文档存储增长都吃磁盘)
|
||
- 内存 ≥ 4 GB(若启用本地 Embedding `sentence-transformers`,建议 8 GB)
|
||
- 出站网络可达所选 LLM / Embedding 服务
|
||
|
||
## 3. 配置 `.env`
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
$EDITOR .env
|
||
```
|
||
|
||
| 键 | 默认值 | 说明 |
|
||
| --- | --- | --- |
|
||
| `MYSQL_ROOT_PASSWORD` | `root_password_change_me` | **必改** |
|
||
| `DB_NAME` / `DB_USER` / `DB_PASSWORD` | `nex_docus` / `nexdocus` / `password_change_me` | 应用库与账号;**必改密码** |
|
||
| `MYSQL_PORT` | `3306` | 宿主机端口;不需要外部访问时建议改成 `127.0.0.1:3306` 或干脆不映射 |
|
||
| `REDIS_PASSWORD` / `REDIS_PORT` / `REDIS_DB` | `redis_password_change_me` / `6379` / `8` | **必改密码** |
|
||
| `SECRET_KEY` | 占位串 | **必改**:`openssl rand -hex 32`。改了会让已登录用户全部失效 |
|
||
| `DEBUG` | `false` | 保持 false(true 会打印全量 SQL) |
|
||
| `BACKEND_PORT` / `FRONTEND_PORT` | `8000` / `8080` | 对外端口;用户访问的是 `FRONTEND_PORT` |
|
||
| `STORAGE_PATH` | `./storage` | 持久化根目录,**建议绝对路径**;MySQL/Redis 数据也放这里 |
|
||
| `CHUNK_SIZE` / `CHUNK_OVERLAP` | `800` / `150` | RAG 分块策略,改动后需重新向量化才生效 |
|
||
| `ADMIN_USERNAME` / `ADMIN_PASSWORD` / `ADMIN_EMAIL` / `ADMIN_NICKNAME` | `admin` / `Admin@123456` / `admin@example.com` / `系统管理员` | **只在第一次建库时生效**,账号已存在则不会改密码 |
|
||
| `DEFAULT_USER_PASSWORD` | `User@123456` | 管理员在「系统管理 → 用户管理」新建用户时的初始密码;**建议改随机值**,否则后端启动会打 `[安全自检]` 告警 |
|
||
| `DISABLE_SSL_VERIFY` | `false` | 仅自签名证书的内部模型服务才开 |
|
||
| `ZVEC_DATA_DIR` | 空 | 留空即 `${STORAGE_ROOT}/vector_index` |
|
||
| `ZVEC_EMBEDDING_DIM` | `1536` | 必须与选定的 embedding 模型维度一致,换模型要重建索引 |
|
||
|
||
> 前端**没有** API 地址配置项:容器内 nginx 固定把 `/api/` 反代到 `backend:8000`。跨域直连后端需自行改 `frontend/nginx.conf`。
|
||
> 这套 `.env`(部署用)与 `backend/.env`(开发用,键名不同)是两套配置,容器里只认前者。
|
||
|
||
## 4. 首次部署
|
||
|
||
```bash
|
||
./scripts/deploy.sh init
|
||
```
|
||
|
||
依次执行:`.env` 检查(没有就从 `.env.example` 生成并中止,等你填)→ Docker 检查 → `pull` → `build --no-cache` → 起 mysql/redis → 等 15s → `run --rm backend python scripts/init_db.py` → `up -d` → 打印访问信息。
|
||
|
||
等价的手工流程:
|
||
|
||
```bash
|
||
docker compose pull
|
||
docker compose build
|
||
docker compose up -d mysql redis
|
||
until docker compose exec -T mysql mysqladmin ping -hlocalhost -uroot -p"$MYSQL_ROOT_PASSWORD" --silent; do sleep 2; done
|
||
docker compose run --rm backend python scripts/init_db.py
|
||
docker compose up -d
|
||
docker compose ps
|
||
```
|
||
|
||
验证:
|
||
|
||
```bash
|
||
curl -fsS http://127.0.0.1:8000/health # {"status":"healthy"}
|
||
docker compose ps # 4 个服务 healthy/running
|
||
open http://<服务器IP>:8080 # 登录页;默认 admin / .env 里的 ADMIN_PASSWORD
|
||
```
|
||
|
||
## 5. 日常运维
|
||
|
||
| 命令 | 作用 |
|
||
| --- | --- |
|
||
| `./scripts/deploy.sh start` / `stop` / `restart` | 起停服务 |
|
||
| `./scripts/deploy.sh status` | 容器状态 |
|
||
| `./scripts/deploy.sh logs` | 跟踪全部日志 |
|
||
| `./scripts/deploy.sh logs backend` | 跟踪单个服务(`mysql` / `redis` / `backend` / `frontend`) |
|
||
| `./scripts/deploy.sh upgrade` | 升级:交互确认 → `git pull` → 停前后端 → `build --no-cache` → 跑 `init_db.py`(自动补列/种子) → `up -d` → `docker image prune -f` |
|
||
| `./scripts/deploy.sh backup` | `mysqldump` 应用库到 `./backups/nex_docus_<时间戳>.sql` |
|
||
| `./scripts/deploy.sh restore <文件>` | 覆盖式恢复数据库(需输入 `yes`) |
|
||
| `./scripts/deploy.sh uninstall` | 删除容器/网络/本地构建镜像;**不会**删除 `STORAGE_PATH` 数据 |
|
||
|
||
> `upgrade` 会执行 `git pull`:如果服务器上有本地改动或未切换分支,请先自行处理。
|
||
|
||
## 6. 备份与恢复
|
||
|
||
文档正文在文件系统、权限在数据库,**两者必须一起备份**,单独还原任何一侧都不算恢复。
|
||
|
||
```bash
|
||
# 备份
|
||
./scripts/deploy.sh backup # 数据库
|
||
tar czf nexdocus_storage_$(date +%F).tar.gz -C "$STORAGE_PATH" . # 文件 + 索引 + MySQL/Redis 数据
|
||
|
||
# 恢复(先起 mysql/redis,再灌数据,最后起应用)
|
||
docker compose up -d mysql redis
|
||
./scripts/deploy.sh restore backups/nex_docus_20260930_120000.sql
|
||
docker compose up -d backend frontend
|
||
```
|
||
|
||
迁移到新机器:拷贝 `storage` 目录 + SQL 备份 + `.env`,新机 `STORAGE_PATH` 指到新目录,恢复 SQL 后直接 `up -d`。项目的磁盘目录名是 `projects.storage_key`(UUID),与项目名解耦,可以整目录搬迁。
|
||
|
||
定期备份建议交给系统 `cron` 或备份软件,注意 `backups/` 也在 `.gitignore` 内,别提交到仓库。
|
||
|
||
## 7. 反向代理与 HTTPS
|
||
|
||
前端容器只监听 80。生产建议前置一层网关做 TLS 与域名:
|
||
|
||
```nginx
|
||
server {
|
||
listen 443 ssl http2;
|
||
server_name docs.example.com;
|
||
|
||
ssl_certificate /etc/ssl/fullchain.pem;
|
||
ssl_certificate_key /etc/ssl/privkey.pem;
|
||
|
||
client_max_body_size 100M; # 附件上传
|
||
|
||
location / {
|
||
proxy_pass http://127.0.0.1:8080;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Upgrade $http_kind;
|
||
proxy_set_header Connection "upgrade";
|
||
}
|
||
}
|
||
```
|
||
|
||
要点:
|
||
- 前端容器内的 nginx 已对 `/api/v1/chat/send/stream`(SSE)与 `/mcp` 关闭缓冲并放大超时;**外层网关若另有 location 命中这两条路径,也要关闭 `proxy_buffering`**,否则 AI 对话会"卡住不出字"。
|
||
- `map $http_upgrade $http_kind { default upgrade; '' close; }` 放在 `http {}` 中(示例已简化)。
|
||
- 上传大小限制要逐层一致(示例 100M)。
|
||
|
||
## 8. 上线前安全清单
|
||
|
||
- [ ] `MYSQL_ROOT_PASSWORD` / `DB_PASSWORD` / `REDIS_PASSWORD` / `SECRET_KEY` 全部改掉默认值
|
||
- [ ] `ADMIN_PASSWORD` / `DEFAULT_USER_PASSWORD` 改为随机值;启动后 `docker compose logs backend | grep 安全自检` **应无输出**(有输出说明仍在用仓库默认值)
|
||
- [ ] 首次登录后修改 `admin` 密码;删除或禁用演示账号
|
||
- [ ] MySQL / Redis 端口不对公网开放(安全组或改绑 `127.0.0.1`)
|
||
- [ ] `DEBUG=false`
|
||
- [ ] TLS 前置,HTTP 跳转 HTTPS
|
||
- [ ] `STORAGE_PATH` 与数据库备份纳入例行备份(数据库里含 Git Token、模型 API Key、分享密码明文)
|
||
- [ ] 磁盘监控:Redis 落盘失败会让整站变只读(见下)
|
||
|
||
## 9. 常见问题
|
||
|
||
| 现象 | 原因 / 处理 |
|
||
| --- | --- |
|
||
| 前端 502 / 一直转圈 | 后端还没通过 `/health`(首次 `init_db.py` 需要时间)。`./scripts/deploy.sh logs backend` 观察 |
|
||
| `backend` 反复重启,日志 `MISCONF ... RDB snapshot` | Redis 服务端落盘失败并拒绝写入 → **磁盘满或目录权限问题**。清磁盘后 `redis-cli CONFIG SET stop-writes-on-bgsave-error no` 只是临时解封,重启 Redis 会复原,必须根治磁盘 |
|
||
| `Access denied for user` | `.env` 与实际库账号不一致;改过 `DB_PASSWORD` 后已初始化的 MySQL 不会自动同步,需要进容器 `ALTER USER` 或删库重建 |
|
||
| 换机器后项目列表空了 | `STORAGE_PATH` 用了相对路径,数据实际写在别的目录 |
|
||
| 改了 `ADMIN_PASSWORD` 但登录仍是旧密码 | 管理员已存在,`init_db.py` 不会重置密码;在「系统管理 → 用户管理」里改 |
|
||
| AI 对话无引用 / 报维度不匹配 | Embedding 模型或 `ZVEC_EMBEDDING_DIM` 变过 → 重新向量化项目(项目页触发全量) |
|
||
| PDF 导出中文乱码 | 后端镜像已内置 `fonts-wqy-microhei`;若自定义基础镜像请自带中文字体 |
|
||
| `docker compose` 提示 `version` 已废弃 | 已从 `docker-compose.yml` 移除该字段;若用老 `docker-compose` v1,建议升级到 Compose v2 |
|
||
|
||
## 10. 本版验证边界
|
||
|
||
- ✅ 已验证:`./scripts/deploy.sh` 的参数路由与脚本语法、Compose 文件解析、后端镜像构建步骤文本与启动命令。
|
||
- ❌ **未在本轮做端到端 `docker compose up` 实测**(缺少可用 Docker 环境)。首个正式部署请把它当作演练:`init → status → /health → 登录 → 建项目 → 上传 → AI 问答 → 备份`,并把结果登记到 [sdd/releases/v1.0.0.md](../sdd/releases/v1.0.0.md) 的验证边界。
|
||
- ⚠️ `frontend/Dockerfile` 目前 `rm -rf package-lock.json && npm install`(历史原因是 Alpine 下 rollup 可选依赖问题),构建**不保证可重现**;确认网络环境稳定后建议改回 `npm ci`。
|