feat: Docker 化部署(nginx-proxy-manager 反代,单域名 /api 路由)

- docker-compose.yml: backend + frontend 两服务,挂 NPM 所在外部网络、不暴露宿主端口;
  data/uploads 用 bind mount 持久化(deploy/ 下,gitignored)
- backend/Dockerfile: python:3.12-slim + 清华 PyPI 镜像(ARG 可覆盖)
- frontend/Dockerfile: 多阶段 node:22 + npm ci + next build(NEXT_PUBLIC_API_BASE 构建期
  注入,npmmirror 默认)→ next start
- .env.example: 全量部署环境变量模板(compose 替换变量 + 后端密钥)
- deploy/README.md: 服务器部署手册(NPM 配置 / DNS / 数据迁移 / 运维 / 大陆网络)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
2026-10-25 15:35:20 +08:00
co-authored by Claude Code
parent 8f9957ad9b
commit f63c0c55c9
7 changed files with 293 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
# 构建上下文精简(避免把巨型/敏感目录发给 docker daemon)
.git
.gitignore
**/.venv
**/__pycache__
**/*.py[cod]
**/node_modules
**/.next
**/.turbo
**/tsconfig.tsbuildinfo
**/next-env.d.ts
# 环境与数据(密钥不进镜像;数据由卷挂载)
.env
.env.*
!.env.example
backend/data
backend/uploads
deploy/data
deploy/uploads
deploy/README.md
*.db
*.db-*
*.epub
*.log
# 本地工具
.idea
.vscode
.DS_Store
+49
View File
@@ -0,0 +1,49 @@
# ===== BookMate 部署环境变量模板 =====
# 复制为仓库根目录的 `.env` 后填写(.env 已被 gitignore,不会提交)。
# 这个文件同时服务两件事:
# 1) docker compose 的 ${NEXT_PUBLIC_API_BASE} / ${NPM_NETWORK} 替换
# 2) backend 容器的 env_file(注入后端所有环境变量)
# —— compose 替换变量 ——
# 前端(浏览器端)调后端 API 的地址:改成你实际用的域名,必须是 https 且带 /api 后缀
#(NPM 里 location /api 反代到 backend)
NEXT_PUBLIC_API_BASE=https://bookmate.example.com/api
# 服务器上 nginx-proxy-manager 容器所在的 docker 网络名:
# 在服务器执行 `docker network ls` 查看,通常是 npm_default 或 nginx-proxy-manager_default
NPM_NETWORK=npm_default
# —— 后端运行与存储(容器内路径,由 docker-compose 卷挂载,一般不用改)——
DATABASE_URL=sqlite+aiosqlite:////app/data/bookmate.db
UPLOAD_DIR=/app/uploads
DATA_DIR=/app/data
API_HOST=0.0.0.0
API_PORT=8000
# 站点访问密码:部署必须设置!留空 = 任何人可访问
SITE_PASSWORD=
# ===== 模型 Provider(至少配一个;也可启动后在网页「管理模型」里添加)=====
DEEPSEEK_API_KEY=
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
DEEPSEEK_MODEL=deepseek-chat
OPENAI_API_KEY=
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4o
ANTHROPIC_API_KEY=
ANTHROPIC_MODEL=claude-sonnet-4-20250514
OPENROUTER_API_KEY=
OPENROUTER_MODEL=openrouter/auto
# ===== 向量检索 & 重排(OpenAI 兼容 API,如硅基流动 / OpenRouter)=====
# embedding_base_url: 支持 OpenAI 兼容 /embeddings 的端点前缀
EMBEDDING_BASE_URL=https://api.siliconflow.cn/v1
EMBEDDING_API_KEY=
EMBEDDING_MODEL=BAAI/bge-m3
# rerank: 硅基流动使用 /v1/rerank,OpenAI 不提供;留空则跳过重排
RERANK_BASE_URL=https://api.siliconflow.cn/v1
RERANK_API_KEY=
RERANK_MODEL=BAAI/bge-reranker-v2-m3
EMBEDDING_BATCH_SIZE=64
+2
View File
@@ -24,6 +24,8 @@ next-env.d.ts
# Local application data (includes book files and saved model credentials)
backend/data/
backend/uploads/
deploy/data/
deploy/uploads/
*.db
*.db-*
*.sqlite*
+23
View File
@@ -0,0 +1,23 @@
# BookMate 后端镜像:FastAPI + SQLAlchemy async + SQLite
# 构建上下文是仓库根目录(docker-compose context: .),COPY 路径相对仓库根。
FROM python:3.12-slim
WORKDIR /app
ENV PYTHONUNBUFFERED=1 \
PYTHONIOENCODING=utf-8 \
PIP_NO_CACHE_DIR=1
# 先装依赖(利用层缓存)。大陆网络默认走清华 PyPI 镜像;可在构建时用 --build-arg 覆盖:
# docker compose build --build-arg PIP_INDEX_URL=https://pypi.org/simple backend
ARG PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
COPY backend/requirements.txt /app/requirements.txt
RUN pip install -i "$PIP_INDEX_URL" -r /app/requirements.txt
# 应用代码;密钥与数据不打包进镜像(.env 由 compose env_file 注入,data/uploads 由卷挂载)
COPY backend/app /app/app
EXPOSE 8000
# 生产用:不挂 --reload
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
+120
View File
@@ -0,0 +1,120 @@
# BookMate 云服务器部署手册(Docker + nginx-proxy-manager)
> 服务器已装 Docker 与 nginx-proxy-manager(NPM)。BookMate 由 `backend`(FastAPI:8000)与
> `frontend`(Next.js:3000)两个容器组成,挂到 NPM 所在网络,由 NPM 用域名反代 + TLS。
> **不暴露任何宿主端口**,不会和你服务器上的其它应用冲突。
## 0. 准备
把仓库放到服务器(二选一):
```bash
# A. git 克隆(推荐,后续更新方便)
git clone https://code.aifunny.ltd/gavindiaz/my_bookmate.git /opt/bookmate
cd /opt/bookmate
# B. 或从本机拷贝(不含 .git 也行)
# scp -r ./bookmate user@server:/opt/
```
## 1. 迁移现有数据(保留书库 / 对话 / 向量)
> 只迁移 `backend/data`(含 `bookmate.db`)和 `backend/uploads`(EPUB 原文件)。
> 先确认本机后端已停止(避免 db 锁),再拷贝。
```bash
# 在服务器上(目录结构照做)
mkdir -p /opt/bookmate/deploy/data /opt/bookmate/deploy/uploads
# 本机执行(把路径换成你的)
scp backend/data/bookmate.db user@server:/opt/bookmate/deploy/data/
scp -r backend/uploads/. user@server:/opt/bookmate/deploy/uploads/
```
不迁移也能跑:服务器从空书库开始,之后在网页里重新导入即可。
## 2. 配置 .env
```bash
cd /opt/bookmate
cp .env.example .env
# 编辑 .env,必改项:
# NEXT_PUBLIC_API_BASE=https://你的域名/api
# NPM_NETWORK= ← 服务器上 `docker network ls` 查到 NPM 所在网络名
# SITE_PASSWORD= ← 站点访问密码(必须设,否则任何人可访问)
# DEEPSEEK_API_KEY / OPENROUTER_API_KEY / EMBEDDING_API_KEY / RERANK_API_KEY 等
```
## 3. 构建并启动
```bash
cd /opt/bookmate
docker compose up -d --build
docker compose ps # 两个容器都应为 Up
# 无 NPM 时直连验证:
curl http://localhost:8000/api/health # {"status":"ok"}
curl http://localhost:3000 # 前端 HTML
```
## 4. NPM 反向代理 + TLS
NPM 网页(通常 http://服务器IP:81)→ Hosts → Add Proxy Host:
- **Domain Names**:`bookmate.example.com`(改成你的)
- **Forward Hostname / Port**:
- 根路径:`frontend` / `3000`
- **Custom Locations**(添加两段,最长前缀优先):
| Location | Forward Hostname | Port |
|---|---:|---:|
| `/api` | `backend` | `8000` |
| `/` | `frontend` | `3000` |
- **SSL**:Request a new SSL Certificate → Let's Encrypt → 勾选 Force SSL。
SSE 流式与文件上传都是普通 HTTP,NPM 无需 websocket 特殊配置。
## 5. DNS
把 `bookmate.example.com` 的 **A 记录**指向服务器公网 IP,等解析生效即可访问
`https://bookmate.example.com`。
## 6. 验证
1. 打开 `https://bookmate.example.com` → 出现登录页 → 输入 SITE_PASSWORD。
2. 书库应显示迁移过来的书;打开一本书能看到迁移的对话。
3. 上传一本新 EPUB → 对话提问 → 确认流式回答与引用正常。
4. 发一条「第二大脑 / AI 助手」消息 → 确认工具(bm_*)能调用。
## 7. 日常运维
```bash
# 更新到最新代码并重建
cd /opt/bookmate
git pull
docker compose up -d --build
# 看日志
docker compose logs -f backend
docker compose logs -f frontend
# 备份(只备份数据即可,镜像可重建)
tar czf bookmate-data-backup.tgz deploy/data deploy/uploads
```
## 大陆网络
Dockerfile 已把国内源烘焙成默认值(可在构建时用 `--build-arg` 覆盖为官方源):
- backend 的 pip:默认 `https://pypi.tuna.tsinghua.edu.cn/simple`
- frontend 的 npm:默认 `https://registry.npmmirror.com`
基础镜像(`python:3.12-slim` / `node:22-alpine`)来自 Docker Hub,大陆直连可能很慢/失败:
- 临时用代理拉一次后即缓存(`docker pull python:3.12-slim && docker pull node:22-alpine`);
- 或给 Docker 配置 registry 镜像加速(如阿里云容器镜像服务),长期更稳。
## 常见问题
- **NPM 连不上容器**:`NPM_NETWORK` 没填对。`docker network ls` 确认 NPM 所在网络名,
填到 `.env` 后 `docker compose down && docker compose up -d` 重建网络连接。
- **页面能开但接口 404**:`/api` 的 Custom Location 没加,或 `NEXT_PUBLIC_API_BASE` 端口/域名
与 NPM 配置不一致(它内联在构建产物里,改了要重新 build)。
- **向量没生效**:确认 `EMBEDDING_API_KEY` / `RERANK_API_KEY` 已填;上传新书后后台会自动生成向量。
+35
View File
@@ -0,0 +1,35 @@
services:
backend:
build:
context: .
dockerfile: backend/Dockerfile
# 后端全部环境变量(含密钥)从根目录 .env 注入;pydantic 会忽略额外键
env_file: ./.env
volumes:
- ./deploy/data:/app/data # SQLite 数据库(迁移时把本机 backend/data 拷到这里)
- ./deploy/uploads:/app/uploads # EPUB 原文件
restart: unless-stopped
networks:
- bookmate
- npm-proxy
frontend:
build:
context: .
dockerfile: frontend/Dockerfile
args:
# 构建期内联到浏览器代码;务必改成你实际使用的域名(带 /api 后缀)
NEXT_PUBLIC_API_BASE: ${NEXT_PUBLIC_API_BASE:-https://bookmate.example.com/api}
depends_on:
- backend
restart: unless-stopped
networks:
- bookmate
- npm-proxy
networks:
bookmate: {} # 两服务互通(backend 内部调用,无宿主端口)
npm-proxy:
external: true
# 服务器上 nginx-proxy-manager 容器所在网络名,用 `docker network ls` 查到后填到 .env 的 NPM_NETWORK
name: ${NPM_NETWORK:-npm_default}
+34
View File
@@ -0,0 +1,34 @@
# BookMate 前端镜像:Next.js(经典 next start 方案,最稳妥)
# 构建上下文是仓库根目录,COPY 路径相对仓库根。
# —— 构建阶段:安装依赖 + 生产构建(NEXT_PUBLIC_API_BASE 在此时内联进浏览器代码)——
FROM node:22-alpine AS builder
WORKDIR /app
# 大陆网络默认走 npmmirror 镜像;可在构建时用 --build-arg 覆盖:
# docker compose build --build-arg NPM_REGISTRY=https://registry.npmjs.org frontend
ARG NPM_REGISTRY=https://registry.npmmirror.com
ENV npm_config_registry="$NPM_REGISTRY"
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci
COPY frontend/ .
ARG NEXT_PUBLIC_API_BASE
ENV NEXT_PUBLIC_API_BASE=$NEXT_PUBLIC_API_BASE
RUN npm run build
# —— 运行阶段:最小化拷贝出 .next / node_modules / public ——
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/package.json /app/package-lock.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/next.config.ts ./
EXPOSE 3000
CMD ["npm", "run", "start"]