fix(docker): 数据改用 Docker 命名卷,修容器打不开数据库的问题
## 现象
容器跑着跑着页面全部 500,应用日志:
sqlite3.OperationalError: unable to open database file
(db.py:33, conn.execute("PRAGMA journal_mode=WAL"))
## 根因(已最小复现)
数据原本用绑定挂载(./data:/app/data)。Windows + Docker Desktop 的绑定挂载走 9p
(mount 里是 type 9p, aname=drvfs;path=C:\)。9p 本身支持 WAL(新建库能开 WAL),
但**宿主的 Windows 进程打开过这个 WAL 库之后**,容器侧缓存的 -shm 映射就失效,
下一次连接无法重建共享内存文件 → 打不开数据库,且**不会自愈**。
复现:
docker compose -f docker-compose.yml -f docker-compose.hostdir.yml up -d
docker compose exec portal python -c "..." # OK, 1665 条
python manage.py stats # 宿主侧纯读一次
docker compose exec portal python -c "..." # ERR unable to open database file
# 只有 docker compose restart portal 才恢复
## 处理
- docker-compose.yml 改用命名卷 wb_data / wb_logs(容器独占 /app/data)
- 新增 docker-compose.hostdir.yml 叠加层:需要宿主目录时用
docker compose -f docker-compose.yml -f docker-compose.hostdir.yml up -d
(注明**只建议 Linux**;Linux 的 bind mount 与容器同一文件系统,无此问题)
- 数据迁移:docker run --rm -v workbuddy-portal_wb_data:/to -v "$PWD/data":/from:ro \
alpine:3.20 sh -c 'cp -a /from/. /to/'
- 文档同步:DEPLOYMENT 2.4/5.4/第六节全部改为命名卷 + 备份恢复用 docker run;
新增第九节「Windows 绑定挂载的坑」(含复现步骤);FAQ、USER-GUIDE、README、CHANGELOG 同步
## 验证
- 宿主跑 manage.py stats 与 smoke.py 之后,容器侧仍能正常读写(此前会立刻失效)
- 容器实例 check_live 56/56;离线 smoke 99/99;hostdir 叠加层 config 校验通过
这个提交包含在:
+45
-11
@@ -32,13 +32,34 @@ docker compose exec portal python /app/docker/healthcheck.py
|
||||
|
||||
### Q:`unable to open database file`
|
||||
|
||||
Linux 宿主机上宿主目录属主与容器内 uid 1000 不一致:
|
||||
分三种情况,先判断你用的是哪种挂载:
|
||||
|
||||
| 场景 | 原因 | 处理 |
|
||||
|---|---|---|
|
||||
| 默认(命名卷) | 极少见;多半是卷被误删或磁盘满 | `docker compose exec portal ls -la /app/data`;`docker compose exec portal df -h /app/data` |
|
||||
| 用了 `docker-compose.hostdir.yml`(**Linux**) | 宿主目录属主与容器内 uid 1000 不一致 | `sudo chown -R 1000:1000 ./data ./logs` |
|
||||
| 用了 `docker-compose.hostdir.yml`(**Windows / Docker Desktop**) | 9p 挂载的固有缺陷,见下条 | 换成默认的命名卷 |
|
||||
|
||||
### Q:容器一开始好的,跑着跑着页面全 500,日志里 `unable to open database file`
|
||||
|
||||
**这是 Windows + Docker Desktop 用绑定挂载(hostdir 叠加层)时的典型症状。**
|
||||
|
||||
宿主机的 Windows 进程只要访问过这个 WAL 库——**哪怕只是 `manage.py stats` 这种纯读**——
|
||||
容器侧下一次打开数据库就会失败,**而且不会自愈**。
|
||||
|
||||
```bash
|
||||
sudo chown -R 1000:1000 ./data ./logs
|
||||
docker compose restart
|
||||
docker compose restart portal # 临时恢复(但宿主再碰一次还会坏)
|
||||
```
|
||||
|
||||
**根治**:不要用 `hostdir` 叠加层,直接用默认的命名卷;需要跑 CLI 就:
|
||||
|
||||
```bash
|
||||
docker compose exec portal python manage.py stats
|
||||
```
|
||||
|
||||
完整复现步骤与原理见
|
||||
[部署与运维指南](DEPLOYMENT.md#windows-绑定挂载的坑容器打不开数据库)。
|
||||
|
||||
### Q:`database is locked` / `disk I/O error`
|
||||
|
||||
| 报错 | 原因 |
|
||||
@@ -206,16 +227,28 @@ docker compose exec portal python manage.py passwd admin 新密码
|
||||
|
||||
### Q:备份怎么做最稳
|
||||
|
||||
数据在命名卷 `workbuddy-portal_wb_data` 里。先 checkpoint 再拷出单个 `.sqlite`:
|
||||
|
||||
```bash
|
||||
# 1) 先 checkpoint,把 WAL 落进主库
|
||||
docker compose exec portal python -c "
|
||||
from workbuddy_portal import db
|
||||
db.connect().execute('PRAGMA wal_checkpoint(TRUNCATE)')"
|
||||
# 2) 拷走
|
||||
cp data/usage.sqlite ~/backup/usage-$(date +%F).sqlite
|
||||
|
||||
docker run --rm \
|
||||
-v workbuddy-portal_wb_data:/data:ro \
|
||||
-v "$PWD/backup":/backup \
|
||||
alpine:3.20 cp /data/usage.sqlite /backup/usage-$(date +%F).sqlite
|
||||
```
|
||||
|
||||
或者整体 `tar` 掉 `data/`(含 `-wal` / `-shm`)——**别把新库配旧 WAL 用**,那会损坏数据。
|
||||
整体打包(含 `instance.json`、`exports/`):
|
||||
|
||||
```bash
|
||||
docker run --rm -v workbuddy-portal_wb_data:/data:ro -v "$PWD/backup":/backup \
|
||||
alpine:3.20 tar czf /backup/wb-data-$(date +%F).tar.gz -C /data .
|
||||
```
|
||||
|
||||
恢复见 [部署指南 6.3](DEPLOYMENT.md#63-恢复)。
|
||||
**别把新库配旧 WAL 用**——会损坏数据(用上面已 checkpoint 的单文件备份最省心)。
|
||||
|
||||
### Q:数据库文件越来越大
|
||||
|
||||
@@ -228,16 +261,17 @@ docker compose exec portal python manage.py vacuum
|
||||
|
||||
### Q:升级会不会丢数据
|
||||
|
||||
不会。数据在宿主机的 `data/`(绑定挂载),`docker compose up -d --build` 只重建容器。
|
||||
但**升级前依然要备份**:`schema.sql` 用 `CREATE TABLE IF NOT EXISTS`,
|
||||
不会。数据在 Docker 命名卷 `workbuddy-portal_wb_data` 里(对应容器内 `/app/data`),
|
||||
`docker compose up -d --build` 只重建容器,不碰卷。
|
||||
但**升级前依然要备份**(见上一条):`schema.sql` 用 `CREATE TABLE IF NOT EXISTS`,
|
||||
加表加索引安全,**改列需要手工迁移**。
|
||||
|
||||
### Q:日志在哪、怎么滚动
|
||||
|
||||
| 位置 | 内容 |
|
||||
|---|---|
|
||||
| `logs/app.log`(挂载到宿主机) | 应用日志,滚动 2 MB × 3 |
|
||||
| `docker compose logs` | 容器 stdout(entrypoint + waitress) |
|
||||
| `docker compose logs portal` | 容器 stdout(entrypoint + waitress) |
|
||||
| 命名卷 `workbuddy-portal_wb_logs` 里的 `app.log` | 应用日志,滚动 2 MB × 3 |
|
||||
| 页面「日志管理」 | 采集逐行日志 + 应用日志尾部 + 操作审计 |
|
||||
|
||||
### Q:想改采集的接口地址(走镜像/代理)
|
||||
|
||||
在新工单中引用
屏蔽一个用户