chore(deploy): docker 镜像源/restart 策略 + .env 修正 + 文档

- 所有 Dockerfile 加 BASE_REGISTRY build-arg,国内拉不到 docker.io
  时可换 daocloud 等镜像源;compose 透传该 arg
- docker-compose: restart 从 on-failure:3 改 unless-stopped(避免短暂
  崩溃后永久打死);gpu compose 补齐 healthcheck/restart/mem_limit
- Dockerfile.complete: supervisord 用 %(ENV_*)s 透传环境变量给 backend
  子进程(之前只白名单 2 个,docker run -e 配的变量后端看不到)
- .env.example: 修正 VITE_API_BASE_URL 端口(8000→8483)、
  WHISPER_MODEL_SIZE medium→tiny(首次启动不被大模型下载卡住)、
  补 Docker 部署说明注释
- README: 新增 Docker 部署常见问题 FAQ(镜像源/restart/数据持久化等)
- CLAUDE.md: 勘误(移除不存在的 messaging/i18n/worker_registry 描述,
  修正 events 路径),补 pytest/typecheck 命令

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
huangjianwu
2026-05-14 19:01:55 +08:00
parent 37f7ee6e15
commit f1b091b846
9 changed files with 176 additions and 30 deletions

View File

@@ -1,12 +1,28 @@
# =============================================================================
# BiliNote 环境变量示例
# Docker 部署cp .env.example .env按需修改然后 docker-compose up --build -d
#
# 注意区分两类变量:
# 1) VITE_* 是【构建时】变量,会被烘进前端 JS bundle。改完必须
# docker-compose build frontend && docker-compose up -d 才会生效,
# 只 docker-compose restart 不行。
# 2) 其他后端变量是【运行时】变量,改完 docker-compose up -d 即可。
#
# 提醒LLM API key 不要写在这里!请部署完成后,从前端「模型供应商」页面录入,
# 这些 key 会保存到 SQLite 数据库(./backend/bili_note.db并随容器持久化。
# =============================================================================
# 通用端口配置
BACKEND_PORT=8483 # 后端端口
FRONTEND_PORT=3015
BACKEND_HOST=0.0.0.0 # 默认为 0.0.0.0,表示监听所有 IP 地址 不建议动
APP_PORT= 3015 # docker 部署时
# 前端访问后端用 (开发环境使用)
VITE_API_BASE_URL=http://127.0.0.1:8000
APP_PORT=3015 # docker 部署时对外暴露端口(浏览器访问的端口)
# 前端访问后端用开发环境直连Docker 部署下走 nginx 代理,此值仅作回退)
VITE_API_BASE_URL=http://127.0.0.1:8483
VITE_SCREENSHOT_BASE_URL=http://127.0.0.1:8483/static/screenshots
VITE_FRONTEND_PORT=3015
# 生产环境配置
ENV=production
STATIC=/static
@@ -14,11 +30,16 @@ OUT_DIR=./static/screenshots
NOTE_OUTPUT_DIR=note_results
IMAGE_BASE_URL=/static/screenshots
DATA_DIR=data
# FFMPEG 配置
# FFMPEG 配置Docker 镜像已内置 ffmpeg留空即可自建/桌面端可填绝对路径)
FFMPEG_BIN_PATH=
# transcriber 相关配置
TRANSCRIBER_TYPE=fast-whisper # fast-whisper/bcut/kuaishou/mlx-whisper(仅Apple平台)/groq
WHISPER_MODEL_SIZE=medium
# 转写器配置
# TRANSCRIBER_TYPE 可选:fast-whisper / bcut / kuaishou / mlx-whisper(仅 Apple Silicon) / groq
TRANSCRIBER_TYPE=fast-whisper
# WHISPER_MODEL_SIZE 默认 tiny (~75MB),首次启动快;想要更高识别质量可在前端
# 「音频转写配置」页切到 base/small/medium/large。直接在这里改大尺寸会触发
# 首次启动下载 ~1.5GB 文件,慢网络或 4GB 内存的容器容易 OOM。
WHISPER_MODEL_SIZE=tiny
GROQ_TRANSCRIBER_MODEL=whisper-large-v3-turbo # groq提供的faster-whisper 默认为 whisper-large-v3-turbo

View File

@@ -1,6 +1,9 @@
# === 前端构建阶段 ===
# Tailwind v4 / Vite 6 需要 Node 20+alpine + pnpm 会按 lockfile 拉 musl native binary。
FROM node:20-alpine AS builder
# BASE_REGISTRY 默认 docker.io国内拉不到可换 daocloud / 阿里云镜像:
# docker-compose build --build-arg BASE_REGISTRY=docker.m.daocloud.io
ARG BASE_REGISTRY=docker.io
FROM ${BASE_REGISTRY}/library/node:20-alpine AS builder
# pnpm pin 到 9.xlockfile 是 v9 生成pnpm 11 要求 Node 22+ 与 node:20 不兼容
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
@@ -16,7 +19,9 @@ COPY ./BillNote_frontend/ ./
RUN pnpm run build
# --- 阶段2使用 nginx 作为静态服务器 ---
FROM nginx:1.25-alpine
# 重新声明 ARG —— buildkit 跨阶段不自动继承
ARG BASE_REGISTRY=docker.io
FROM ${BASE_REGISTRY}/library/nginx:1.25-alpine
RUN rm -rf /etc/nginx/conf.d/default.conf
COPY ./BillNote_frontend/deploy/default.conf /etc/nginx/conf.d/default.conf

View File

@@ -13,6 +13,8 @@ BiliNote is an AI video note generation tool. It extracts content from video lin
cd backend
pip install -r requirements.txt
python main.py # Starts on 0.0.0.0:8483
pytest # Run tests in backend/tests/
pytest tests/test_request_chunker.py::test_name # Run a single test
```
### Frontend (React 19 + Vite + TypeScript)
@@ -43,6 +45,8 @@ pnpm install
pnpm dev # watch mode → ./extension/
pnpm build # production build → ./extension/
pnpm typecheck
pnpm test # Vitest unit tests
pnpm test:e2e # Playwright e2e
```
Load unpacked at `chrome://extensions/` → select `BillNote_extension/extension/`. Talks to the same backend at `http://localhost:8483` (configurable in the options page). CORS in `backend/main.py` already accepts `chrome-extension://` and `moz-extension://` via regex.
@@ -56,15 +60,15 @@ Load unpacked at `chrome://extensions/` → select `BillNote_extension/extension
- `chat_service.py` + `chat_tools.py` + `vector_store.py` — RAG-based AI Q&A with Function Calling, indexing transcripts and video metadata
- `cookie_manager.py` — per-platform cookie storage; injected into yt-dlp by downloaders (e.g. Bilibili)
- `transcriber_config_manager.py` — persisted transcriber settings
- `worker_registry.py`**optional** Nacos registration + heartbeat for distributed worker mode (no-op when `NACOS_SERVER_ADDR` unset)
- `app/messaging/`**optional** RabbitMQ producer/consumer publishing task progress/results to `bilinote.task.feedback` exchange. Silently degrades when `RABBITMQ_URL` is unset; always import-safe.
- `app/downloaders/` — Platform adapters (bilibili, youtube, douyin, kuaishou, local) with shared `base.py` interface
- `app/transcriber/` — Speech-to-text engines (fast-whisper, groq, bcut, kuaishou, mlx-whisper) with factory in `transcriber_provider.py`. YouTube path prefers existing subtitles and skips audio download when available.
- `app/gpt/` — LLM integration with factory pattern (`gpt_factory.py`), prompt templates (`prompt.py`, `prompt_builder.py`), and `request_chunker.py` for long transcripts
- `app/db/` — SQLite + SQLAlchemy: DAO pattern (`provider_dao.py`, `model_dao.py`, `video_task_dao.py`), models in `models/`
- `app/utils/``response.py` (ResponseWrapper for consistent JSON), `video_helper.py` (screenshots via FFmpeg), `export.py` (PDF/DOCX), `ppt_generator.py`, `minio_client.py`
- `app/i18n/` — backend localization
- `events/` (root level) — Blinker signal system for post-processing (e.g., temp file cleanup after transcription)
- `app/validators/video_url_validator.py` — URL → platform detection (mirrored client-side in the extension)
- `app/exceptions/``BizException` + handlers wired in `main.py` via `register_exception_handlers`
- `backend/events/` — Blinker signal system for post-processing (e.g., temp file cleanup after transcription); registered in `lifespan` startup
- `backend/ffmpeg_helper.py``ensure_ffmpeg_or_raise` is called at startup; respects `FFMPEG_BIN_PATH`
**Frontend** (`BillNote_frontend/src/`) — React 19 + Vite + Tailwind + shadcn/ui:
- `pages/HomePage/` — Main note generation UI: `NoteForm.tsx` (input), `MarkdownViewer.tsx` (preview), `MarkmapComponent.tsx` (mind map)
@@ -94,8 +98,8 @@ Load unpacked at `chrome://extensions/` → select `BillNote_extension/extension
- **Environment**: Root `.env` (copy from `.env.example`). LLM API keys are configured through the UI, not env vars.
- **Database**: SQLite at `backend/app/db/bili_note.db`, auto-initialized on first run
- **FFmpeg**: Required system dependency for video/audio processing
- **Vite proxy**: Dev server proxies `/api` and `/static` to backend (configured in `vite.config.ts`, reads env from parent dir)
- **Distributed mode (optional)**: Setting `NACOS_SERVER_ADDR` enables Nacos worker registration; setting `RABBITMQ_URL` enables MQ feedback. Both are no-ops when unset — single-node deployment works without either. Other knobs: `WORKER_ID`, `WORKER_SELF_URL`, `WORKER_MAX_CONCURRENT`, `TASK_MAX_WORKERS`.
- **Vite proxy**: Dev server proxies `/api` and `/static` to backend (configured in `vite.config.ts`, reads env from parent dir; falls back to current dir when `DOCKER_BUILD` is set)
- **CORS**: `backend/main.py` uses a regex (`CORS_ORIGIN_REGEX`) that allows localhost, `tauri.localhost`, and `chrome-extension://` / `moz-extension://` origins — required for the desktop app and the browser extension.
## Code Style

View File

@@ -1,5 +1,9 @@
# BASE_REGISTRY 默认 docker.io国内拉不到可换镜像源
# docker build --build-arg BASE_REGISTRY=docker.m.daocloud.io -f Dockerfile.complete .
ARG BASE_REGISTRY=docker.io
# === 阶段1构建 Backend ===
FROM python:3.11-slim AS backend-builder
FROM ${BASE_REGISTRY}/library/python:3.11-slim AS backend-builder
ARG APT_MIRROR=mirrors.tuna.tsinghua.edu.cn
ARG PIP_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple
@@ -28,7 +32,8 @@ COPY ./backend /tmp/backend
# === 阶段2构建 Frontend ===
# Node 18-alpine 跑不动 Tailwind v4 / Vite 6前者要求 Node 20+,后者推荐 Node 20+
# 升到 node:20-alpine。alpine 走 muslpnpm 会按 lockfile 拉 *-linux-x64-musl native binary。
FROM node:20-alpine AS frontend-builder
ARG BASE_REGISTRY=docker.io
FROM ${BASE_REGISTRY}/library/node:20-alpine AS frontend-builder
# pnpm 版本 pin 到 9 系列:
# - lockfile (BillNote_frontend/pnpm-lock.yaml) 是 lockfileVersion '9.0',由 pnpm 9 生成
@@ -50,7 +55,8 @@ ENV DOCKER_BUILD=1
RUN pnpm run build
# === 阶段3完整应用镜像 ===
FROM python:3.11-slim
ARG BASE_REGISTRY=docker.io
FROM ${BASE_REGISTRY}/library/python:3.11-slim
ARG APT_MIRROR=mirrors.tuna.tsinghua.edu.cn
@@ -85,6 +91,10 @@ RUN rm -rf /etc/nginx/conf.d/default.conf
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
# 创建 supervisor 配置
# 关键点supervisord 默认 *不* 把自己的环境变量传给子进程。
# 在 [supervisord] 块用 environment= 设兜底默认值;在 [program:backend] 用
# %(ENV_*)s 显式引用,等价于「把 host 通过 docker run -e 或 env_file 传进来的
# 变量再透传给 python main.py」。漏掉这一步就是用户「改 .env 没反应」的根因。
RUN mkdir -p /var/log/supervisor
COPY <<EOF /etc/supervisor/conf.d/supervisord.conf
[supervisord]
@@ -92,6 +102,7 @@ nodaemon=true
user=root
logfile=/var/log/supervisor/supervisord.log
pidfile=/var/run/supervisord.pid
environment=BACKEND_PORT="8483",BACKEND_HOST="0.0.0.0",TRANSCRIBER_TYPE="fast-whisper",WHISPER_MODEL_SIZE="tiny",FFMPEG_BIN_PATH="",HF_ENDPOINT="https://hf-mirror.com",STATIC="/static",OUT_DIR="./static/screenshots",DATA_DIR="data",NOTE_OUTPUT_DIR="note_results",IMAGE_BASE_URL="/static/screenshots",ENV="production",GROQ_TRANSCRIBER_MODEL="whisper-large-v3-turbo"
[program:nginx]
command=nginx -g "daemon off;"
@@ -107,7 +118,7 @@ stdout_logfile=/var/log/supervisor/backend.log
stderr_logfile=/var/log/supervisor/backend.log
autorestart=true
priority=20
environment=BACKEND_PORT="8483",BACKEND_HOST="0.0.0.0"
environment=BACKEND_PORT="%(ENV_BACKEND_PORT)s",BACKEND_HOST="%(ENV_BACKEND_HOST)s",TRANSCRIBER_TYPE="%(ENV_TRANSCRIBER_TYPE)s",WHISPER_MODEL_SIZE="%(ENV_WHISPER_MODEL_SIZE)s",FFMPEG_BIN_PATH="%(ENV_FFMPEG_BIN_PATH)s",HF_ENDPOINT="%(ENV_HF_ENDPOINT)s",STATIC="%(ENV_STATIC)s",OUT_DIR="%(ENV_OUT_DIR)s",DATA_DIR="%(ENV_DATA_DIR)s",NOTE_OUTPUT_DIR="%(ENV_NOTE_OUTPUT_DIR)s",IMAGE_BASE_URL="%(ENV_IMAGE_BASE_URL)s",ENV="%(ENV_ENV)s",GROQ_TRANSCRIBER_MODEL="%(ENV_GROQ_TRANSCRIBER_MODEL)s"
EOF
# 修改 nginx 配置以使用本地 backend
@@ -115,5 +126,9 @@ RUN sed -i 's/proxy_pass http:\/\/backend:8483/proxy_pass http:\/\/127.0.0.1:848
sed -i 's/proxy_pass http:\/\/frontend:80/proxy_pass http:\/\/127.0.0.1:8080/g' /etc/nginx/conf.d/default.conf
# 启动 supervisor
# 推荐启动方式(覆盖默认 env
# docker run -d --name bilinote --env-file .env -p 8080:80 bilinote-aio
# 单个变量覆盖:
# docker run -d -e TRANSCRIBER_TYPE=groq -e WHISPER_MODEL_SIZE=base ...
EXPOSE 80
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

View File

@@ -143,13 +143,74 @@ docker run -d -p 80:80 \
也可以使用 docker-compose 本地构建:
```bash
# 标准部署
docker-compose up -d
cp .env.example .env # 第一次部署务必先创建 .env否则 BACKEND_PORT/APP_PORT 等变量为空会启动失败
docker-compose up --build -d
# GPU 加速部署(需要 NVIDIA GPU
docker-compose -f docker-compose.gpu.yml up -d
# GPU 加速部署(需要 NVIDIA GPU + NVIDIA Container Toolkit
docker-compose -f docker-compose.gpu.yml up --build -d
```
#### Docker 部署常见问题FAQ
社区反馈最集中的几个坑,遇到先按下面排查:
**0. 国内拉不到 docker.iobuild 阶段报 `dial tcp ... i/o timeout`**
`docker-compose build``python:3.11-slim` / `node:20-alpine` / `nginx:1.25-alpine` 时连 `auth.docker.io` 超时。三种解法,按推荐顺序:
- **方法 A直接用预构建镜像最省事**——不要本地 build跳到上面的 `docker pull ghcr.io/jefferyhcool/bilinote:latest` 路径ghcr.io 在国内通常比 docker.io 顺。
- **方法 B配置 Docker daemon 镜像加速器**——编辑 `~/.docker/daemon.json`Linux 在 `/etc/docker/daemon.json`),加:
```json
{
"registry-mirrors": ["https://docker.m.daocloud.io"]
}
```
然后重启 Docker Desktop / `sudo systemctl restart docker`。这是一劳永逸的做法。
- **方法 C临时切换 base image 镜像源**——本项目所有 Dockerfile 都暴露了 `BASE_REGISTRY` build-arg
```bash
BASE_REGISTRY=docker.m.daocloud.io docker-compose build
docker-compose up -d
```
或永久写到 `.env``echo 'BASE_REGISTRY=docker.m.daocloud.io' >> .env`。
注意Chinese 公共 docker 镜像源时常被关停2025-2026 之间可用的列表会变;如果 `docker.m.daocloud.io` 不通,搜一下"Docker 镜像加速 可用"找最新可用源即可。
**1. 容器一直 restart / unhealthy**
先看后端日志:
```bash
docker logs -f bilinote-backend
```
后端启动会按顺序打印 `[startup 1/5] ... [startup 5/5] 启动完成`。若日志卡在某一步或出现 `[startup FAILED]`,就是那一步的问题,常见:
- **卡在 `[startup 3/5]`**:转写器配置读不到。检查 `.env` 里 `TRANSCRIBER_TYPE` 是否写错,`mlx-whisper` 只能在 Apple Silicon 用Linux/Docker 请用 `fast-whisper` 或 `groq`。
- **首次跑视频时容器被 kill**whisper 模型下载触发 OOM。先把 `.env` 里 `WHISPER_MODEL_SIZE` 改成 `tiny`,跑通后再去前端「音频转写配置」里逐档升。
**2. 改了 `.env` 没生效**
区分两类变量:
- `VITE_*` 是**构建时**变量(前端 bundle 里硬编码),改完必须 `docker-compose build frontend && docker-compose up -d`。只 `restart` 不会重新打包。
- 其他后端变量(`TRANSCRIBER_TYPE`、`WHISPER_MODEL_SIZE`、`FFMPEG_BIN_PATH` 等)是**运行时**变量,改完 `docker-compose up -d` 即可。
注意:**LLM API key 不要写 `.env`**,从前端「模型供应商」页面录入,会保存到 SQLite 数据库并持久化。
**3. 数据存在哪?删容器会丢吗?**
`docker-compose` 用的是 `./backend:/app` 绑挂,下面这些文件都在宿主机的 `./backend/` 目录里、删容器不会丢:
- `./backend/bili_note.db` —— SQLite 库(含 LLM 供应商配置、笔记历史)
- `./backend/config/transcriber.json` —— 转写器运行时配置
- `./backend/static/screenshots/` —— 视频截图
- `./backend/uploads/` —— 上传的本地视频
要彻底重置就 `docker-compose down && rm backend/bili_note.db backend/config/transcriber.json`。
**4. 前端打开是空白页 / 报 502**
通常是 nginx 起来了但 backend 还没 healthy。`docker ps` 看 backend 容器 STATUS 是不是 `(healthy)`;若长期 `(unhealthy)`,按问题 1 排查后端日志。
**5. 不要用 `restart: on-failure:N`**
如果你 fork 后改过 compose 文件、把 restart 策略改成了 `on-failure:3`:任何 3 次连续崩溃都会让容器永远不再启动,之后改 `.env` 也没用。本项目自带的 compose 已经统一用 `unless-stopped`。
### 方式二:源码部署
#### 1. 克隆仓库

View File

@@ -1,4 +1,8 @@
FROM python:3.11-slim
# BASE_REGISTRY 默认走 docker.io国内拉不到 docker.io 时可换 daocloud / 阿里云 / 自建镜像源:
# docker-compose build --build-arg BASE_REGISTRY=docker.m.daocloud.io
# 或写到 docker-compose.yml 的 build.args / 环境变量里
ARG BASE_REGISTRY=docker.io
FROM ${BASE_REGISTRY}/library/python:3.11-slim
ARG APT_MIRROR=mirrors.tuna.tsinghua.edu.cn
ARG PIP_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple

View File

@@ -1,4 +1,6 @@
FROM nvidia/cuda:12.4.1-cudnn-runtime-ubuntu22.04
# BASE_REGISTRY 默认走 docker.io国内可换 daocloud / 阿里云镜像(注意所选镜像需支持 nvidia/cuda 命名空间)
ARG BASE_REGISTRY=docker.io
FROM ${BASE_REGISTRY}/nvidia/cuda:12.4.1-cudnn-runtime-ubuntu22.04
ARG APT_MIRROR=mirrors.tuna.tsinghua.edu.cn
ARG PIP_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple

View File

@@ -6,6 +6,8 @@ services:
context: .
dockerfile: backend/Dockerfile.gpu
args:
# 国内拉不到 docker.io 时设置 BASE_REGISTRY注意所选镜像需要支持 nvidia/cuda 命名空间
BASE_REGISTRY: ${BASE_REGISTRY:-docker.io}
APT_MIRROR: ${APT_MIRROR:-mirrors.tuna.tsinghua.edu.cn}
PIP_INDEX: ${PIP_INDEX:-https://pypi.tuna.tsinghua.edu.cn/simple}
env_file:
@@ -14,9 +16,20 @@ services:
- BACKEND_PORT=${BACKEND_PORT}
- BACKEND_HOST=${BACKEND_HOST}
volumes:
# 同 docker-compose.yml./backend 绑到 /appDB / 转写器配置 / 截图 / 上传都持久化
- ./backend:/app
expose:
- "${BACKEND_PORT}" # 不再对外暴露,用于 nginx 内部通信
# 用 unless-stopped 避免短暂崩溃把容器永久打死后再也读不到 .env 修改
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:${BACKEND_PORT}/api/sys_health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s # GPU 镜像首次加载 CUDA 比 CPU 慢,给久一点
# GPU 部署默认跑较大模型,把内存限制提到 8g 避免 host OOM
mem_limit: 8g
deploy:
resources:
reservations:
@@ -30,10 +43,14 @@ services:
build:
context: .
dockerfile: BillNote_frontend/Dockerfile
args:
BASE_REGISTRY: ${BASE_REGISTRY:-docker.io}
env_file:
- .env
expose:
- "80" # 不暴露给宿主机,只供 nginx 访问
restart: unless-stopped
mem_limit: 512m
nginx:
container_name: bilinote-nginx
@@ -43,5 +60,9 @@ services:
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- backend
- frontend
backend:
condition: service_healthy
frontend:
condition: service_started
restart: unless-stopped
mem_limit: 256m

View File

@@ -6,6 +6,8 @@ services:
context: .
dockerfile: backend/Dockerfile
args:
# 国内拉不到 docker.io 时设置 BASE_REGISTRY=docker.m.daocloud.io或其他可用镜像
BASE_REGISTRY: ${BASE_REGISTRY:-docker.io}
APT_MIRROR: ${APT_MIRROR:-mirrors.tuna.tsinghua.edu.cn}
PIP_INDEX: ${PIP_INDEX:-https://pypi.tuna.tsinghua.edu.cn/simple}
env_file:
@@ -14,16 +16,25 @@ services:
- BACKEND_PORT=${BACKEND_PORT}
- BACKEND_HOST=${BACKEND_HOST}
volumes:
# 把整个 backend/ 目录绑到 /app意味着这些都持久化到宿主机、删容器不丢
# ./backend/bili_note.db — SQLite 数据库(含 LLM 供应商配置、笔记历史)
# ./backend/config/transcriber.json — 转写器运行时配置
# ./backend/static/screenshots/ — 视频截图
# ./backend/uploads/ — 上传的本地视频
- ./backend:/app
expose:
- "${BACKEND_PORT}" # 不再对外暴露,用于 nginx 内部通信
restart: on-failure:3
# 用 unless-stopped 而非 on-failure:N避免任何短暂崩溃把容器永久打死后
# 再也接收不到用户修过的 .env。手动 docker-compose stop 仍可正常停下。
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:${BACKEND_PORT}/api/sys_health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
# WHISPER_MODEL_SIZE 选 medium 及以上请把这里调到 8g+
# 否则首次模型加载时容易被 host OOM-killer 干掉。
mem_limit: 4g
frontend:
@@ -31,11 +42,13 @@ services:
build:
context: .
dockerfile: BillNote_frontend/Dockerfile
args:
BASE_REGISTRY: ${BASE_REGISTRY:-docker.io}
env_file:
- .env
expose:
- "80" # 不暴露给宿主机,只供 nginx 访问
restart: on-failure:3
restart: unless-stopped
mem_limit: 512m
nginx:
@@ -50,5 +63,5 @@ services:
condition: service_healthy
frontend:
condition: service_started
restart: on-failure:3
restart: unless-stopped
mem_limit: 256m