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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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*
|
||||
|
||||
@@ -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"]
|
||||
@@ -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` 已填;上传新书后后台会自动生成向量。
|
||||
@@ -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}
|
||||
@@ -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"]
|
||||
Reference in New Issue
Block a user