今天把 Open WebUI 的文件残留清干净了

今天把 Open WebUI 的文件残留清干净了

记录一次 Open WebUI Docker 数据清理:volume 改名、文件列表残留、RAG 模型缓存,还有我为什么最后改用 docker exec。

今天下午本来只想清一下 Open WebUI 里的聊天文件。

说白了,就是那些生成图片、上传文件、文件管理里一长串 generated-image.png。我以为最多几条命令就完事,结果越清越不对:目录空了,界面还在;数据库查了,前端还显示;刚把文件列表弄干净,Open WebUI 又因为 embedding 模型缓存被删开始报错。

当时心态有点炸。很多自部署工具都是这样,表面上看是一个按钮、一个目录,背后连着数据库、文件目录、缓存、模型和前端状态。少清一处,界面就继续装作什么都没发生。

这张图很像今天的状态:一边扫文件,一边盯数据库。
这张图很像今天的状态:一边扫文件,一边盯数据库。

报错还好,至少它承认有事。最折磨的是界面还在,像刚才那些命令都白敲了。

先把 volume 名字改短

一开始让我不舒服的是 volume 名字:

txt
openwebui_openwebui_open-webui

它太长了,看着就容易删错。原因倒不复杂,Docker Compose 会按下面这个规则给卷加前缀:

txt
项目名_卷名

原来的卷名里已经带了 openwebui,再被 compose 项目名套一层,就成了这串东西。后来我把 compose 里的 volume 改成显式命名:

compose.yamlyaml
services:
  open-webui:
    image: ghcr.io/open-webui/open-webui:latest
    container_name: openwebui
    ports:
      - "127.0.0.1:3000:8080"
    volumes:
      - open-webui:/app/backend/data

volumes:
  open-webui:
    name: open-webui
    driver: local

管用的是:

yaml
name: open-webui

这样 Docker 最后创建出来的卷就叫 open-webui,不会再自动变成 openwebui_open-webui

别手动改 Docker volume 目录名

/var/lib/docker/volumes/ 下面不是普通文件夹管理区。Docker 自己有元数据,直接改目录名不等于改 volume 名,反而可能让容器找不到数据。

改完以后,数据位置就清楚了:

txt
容器内:/app/backend/data
宿主机:/var/lib/docker/volumes/open-webui/_data

这步本身没什么技术含量,但后面少了很多疑神疑鬼。要删东西时,先知道自己正在碰哪个卷。

uploads 空了,文件列表还在

卡住我的其实是文件管理。

我先删了上传目录:

#
rm -rf /var/lib/docker/volumes/open-webui/_data/uploads/*

然后打开 Open WebUI,文件列表里还是一堆 generated-image.png。有 png,也有 gif,日期全是今天。

那一刻很容易怀疑自己删错了。于是开始查:

bash
docker inspect openwebui --format '{{range .Mounts}}{{println .Name "->" .Destination}}{{end}}'

确认结果没问题:

txt
open-webui -> /app/backend/data

也就是说,容器确实用的是这个卷,路径也没错。问题不在目录。

后来才想明白:Open WebUI 的文件列表不是现扫 uploads/ 得来的。uploads/ 只是实体文件,UI 读的是数据库里的文件记录。文件没了,数据库还记着,界面当然继续显示。

删目录

uploads/,实体图片没了。

查挂载

查 volume,确认容器确实挂着 open-webui

查数据库

进正在运行的容器,直接查 /app/backend/data/webui.db

最后定位问题靠的是这条:

bash
docker exec -i openwebui python - <<'PY'
import sqlite3

con = sqlite3.connect("/app/backend/data/webui.db")
cur = con.cursor()

cur.execute("SELECT COUNT(*) FROM file")
print("file", cur.fetchone()[0])

cur.execute('SELECT id, filename, path FROM "file" ORDER BY created_at DESC LIMIT 5')
for row in cur.fetchall():
    print(row)

con.close()
PY

输出里很直接,file 表里就是那些 generated-image.png。路径也都指向:

txt
/app/backend/data/uploads/xxx_generated-image.png

到这里就不用再猜了。界面上的残留在 webui.dbfile 表里。

别再绕临时容器

我前面绕了几次临时容器,又是 main 镜像,又是 latest 镜像,还要手动挂 volume。能做,但没必要。

后面我换了思路:既然 UI 是当前这个容器跑出来的,那就进这个容器查库。它正在用哪个数据库,我就处理哪个数据库。

bash
docker exec -i openwebui python - <<'PY'
import os, shutil, sqlite3

db = "/app/backend/data/webui.db"
con = sqlite3.connect(db)
cur = con.cursor()
cur.execute("PRAGMA foreign_keys=OFF")

for table in ["channel_file", "chat_file", "knowledge_file", "document", "file"]:
    cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table,))
    if cur.fetchone():
        cur.execute(f'SELECT COUNT(*) FROM "{table}"')
        print("before", table, cur.fetchone()[0])
        cur.execute(f'DELETE FROM "{table}"')
        print("cleared", table)

con.commit()
con.execute("VACUUM")
con.close()

for p in ["/app/backend/data/uploads", "/app/backend/data/vector_db"]:
    if os.path.isdir(p):
        shutil.rmtree(p)
    os.makedirs(p, exist_ok=True)
    print("cleared", p)

print("done")
PY

然后重启:

#
docker restart openwebui

这次再进文件管理,空了。

为什么这条命令靠谱

它不需要猜 volume,不需要新拉镜像,也不需要关心临时容器用 main 还是 latest。它直接进当前正在跑的容器,处理当前容器正在用的 /app/backend/data/webui.db

cache 不能乱删

文件清完以后,我又顺手踩了另一个坑。

我之前把 /app/backend/data/cache 也清了。结果 Open WebUI 启动时开始报:

txt
Cannot find an appropriate cached snapshot folder
Network is unreachable
No embedding model is loaded

这不是文件管理残留,而是 RAG 的 embedding 模型缓存没了。

Open WebUI 默认会找这个模型:

txt
sentence-transformers/all-MiniLM-L6-v2

我当时 compose 里配了:

yaml
RAG_EMBEDDING_MODEL_AUTO_UPDATE=false

一开始我以为这就是“不下载模型”。其实不是。它更接近“不自动更新模型”。本地缓存还在时,Open WebUI 可以直接用;缓存被删了,它还是得找模型。找不到,又连不上 HuggingFace,就启动失败。

最后是用镜像源重新下载了一次:

bash
docker run --rm \
  -e HF_ENDPOINT=https://hf-mirror.com \
  -e HF_HUB_OFFLINE=0 \
  -v open-webui:/data \
  --entrypoint python \
  ghcr.io/open-webui/open-webui:latest - <<'PY'
from huggingface_hub import snapshot_download

snapshot_download(
    repo_id="sentence-transformers/all-MiniLM-L6-v2",
    cache_dir="/data/cache/embedding/models",
    local_files_only=False,
)

print("done")
PY

看到日志里出现这两行,我才放心:

txt
Loading weights: 100%
Started server process

以后我不会再把 cache 当垃圾目录清了。

以后只清这些

清文件时处理 uploadsvector_db 和数据库里的文件表就够了。cache 里可能有 embedding 模型,删掉以后还要重新下载。

我为什么绕了一圈

回头看,问题不算复杂。我绕路,是因为一开始把几件事混在了一起。

  • uploads/ 里放的是实体文件。
  • webui.dbfile 表决定文件管理里显示什么。
  • chat_fileknowledge_filechannel_file 是关联表,也要一起清。
  • vector_db 是知识库向量数据。
  • cache 里可能有 RAG 模型,不该顺手删。

这次最有用的教训其实很土:能 docker exec 就别在外面猜。

txt
查挂载:docker inspect openwebui
查文件表:docker exec openwebui python
清记录:DELETE FROM file
清实体:uploads
清向量:vector_db
保留模型:cache

这些命令不优雅,但排障时优雅不重要。少猜一次路径,少拉一次不相关镜像,比什么都强。

留一份短记忆

最后我把配置写到本地:

txt
C:\Users\Mius\Desktop\项目1\openwebui\openwebui-config.md

里面只记几行:

txt
容器名称:openwebui
镜像:ghcr.io/open-webui/open-webui:latest
端口:3000 -> 8080/tcp
Volume:open-webui
数据目录:/var/lib/docker/volumes/open-webui/_data

这类东西写太多反而没用。以后真出问题,能马上看到容器名、卷名和数据目录就够了。

今天这事像整理抽屉。文件本体是纸,数据库记录是索引卡。纸扔了,索引卡还在,柜台当然还说有货。

下次再清 Open WebUI,我会先看两处:容器到底挂着哪个 volume,UI 这块数据到底是从目录读,还是从数据库读。

至于 cache,以后我会单独看待。它不是垃圾桶,里面可能有模型。先确认,再动手。省时间,也省火气。

新故事即将发生
域名走 Cloudflare 优选后,怎么配缓存规则

评论区

评论加载中...