[{"data":1,"prerenderedAt":520},["ShallowReactive",2],{"content:\u002F2026\u002Fopenwebui-cleanup-journey":3,"surround:\u002F2026\u002Fopenwebui-cleanup-journey":514},{"id":4,"title":5,"body":6,"categories":486,"date":488,"description":489,"draft":490,"extension":491,"image":28,"meta":492,"navigation":494,"path":495,"permalink":496,"published":496,"readingTime":497,"recommend":496,"references":496,"seo":502,"sitemap":503,"stem":504,"tags":505,"type":511,"updated":512,"__hash__":513},"content\u002Fposts\u002F2026\u002Fopenwebui-cleanup-journey.md","今天把 Open WebUI 的文件残留清干净了",{"type":7,"value":8,"toc":476},"minimark",[9,13,21,24,29,36,41,44,54,57,63,70,79,82,88,99,110,113,119,122,126,129,132,137,143,146,154,157,163,166,176,205,208,214,224,230,240,243,254,257,263,266,269,272,287,291,294,301,307,310,313,319,322,328,331,334,340,343,349,356,374,377,380,421,428,434,437,440,443,449,452,458,461,467,470],[10,11,12],"p",{},"今天下午本来只想清一下 Open WebUI 里的聊天文件。",[10,14,15,16,20],{},"说白了，就是那些生成图片、上传文件、文件管理里一长串 ",[17,18,19],"code",{"code":19},"generated-image.png","。我以为最多几条命令就完事，结果越清越不对：目录空了，界面还在；数据库查了，前端还显示；刚把文件列表弄干净，Open WebUI 又因为 embedding 模型缓存被删开始报错。",[10,22,23],{},"当时心态有点炸。很多自部署工具都是这样，表面上看是一个按钮、一个目录，背后连着数据库、文件目录、缓存、模型和前端状态。少清一处，界面就继续装作什么都没发生。",[25,26],"pic",{"caption":27,"src":28},"这张图很像今天的状态：一边扫文件，一边盯数据库。","\u002Fassets\u002Fposts\u002Fopenwebui-cleanup.png",[30,31,33],"quote",{"icon":32},"tabler:terminal-2",[10,34,35],{},"报错还好，至少它承认有事。最折磨的是界面还在，像刚才那些命令都白敲了。",[37,38,40],"h2",{"id":39},"先把-volume-名字改短","先把 volume 名字改短",[10,42,43],{},"一开始让我不舒服的是 volume 名字：",[45,46,52],"pre",{"className":47,"code":49,"language":50,"meta":51},[48],"language-txt","openwebui_openwebui_open-webui\n","txt","",[17,53,49],{"__ignoreMap":51},[10,55,56],{},"它太长了，看着就容易删错。原因倒不复杂，Docker Compose 会按下面这个规则给卷加前缀：",[45,58,61],{"className":59,"code":60,"language":50,"meta":51},[48],"项目名_卷名\n",[17,62,60],{"__ignoreMap":51},[10,64,65,66,69],{},"原来的卷名里已经带了 ",[17,67,68],{"code":68},"openwebui","，再被 compose 项目名套一层，就成了这串东西。后来我把 compose 里的 volume 改成显式命名：",[45,71,77],{"className":72,"code":74,"filename":75,"language":76,"meta":51},[73],"language-yaml","services:\n  open-webui:\n    image: ghcr.io\u002Fopen-webui\u002Fopen-webui:latest\n    container_name: openwebui\n    ports:\n      - \"127.0.0.1:3000:8080\"\n    volumes:\n      - open-webui:\u002Fapp\u002Fbackend\u002Fdata\n\nvolumes:\n  open-webui:\n    name: open-webui\n    driver: local\n","compose.yaml","yaml",[17,78,74],{"__ignoreMap":51},[10,80,81],{},"管用的是：",[45,83,86],{"className":84,"code":85,"language":76,"meta":51},[73],"name: open-webui\n",[17,87,85],{"__ignoreMap":51},[10,89,90,91,94,95,98],{},"这样 Docker 最后创建出来的卷就叫 ",[17,92,93],{"code":93},"open-webui","，不会再自动变成 ",[17,96,97],{"code":97},"openwebui_open-webui","。",[100,101,104],"alert",{"title":102,"type":103},"别手动改 Docker volume 目录名","warning",[10,105,106,109],{},[17,107,108],{"code":108},"\u002Fvar\u002Flib\u002Fdocker\u002Fvolumes\u002F"," 下面不是普通文件夹管理区。Docker 自己有元数据，直接改目录名不等于改 volume 名，反而可能让容器找不到数据。",[10,111,112],{},"改完以后，数据位置就清楚了：",[45,114,117],{"className":115,"code":116,"language":50,"meta":51},[48],"容器内：\u002Fapp\u002Fbackend\u002Fdata\n宿主机：\u002Fvar\u002Flib\u002Fdocker\u002Fvolumes\u002Fopen-webui\u002F_data\n",[17,118,116],{"__ignoreMap":51},[10,120,121],{},"这步本身没什么技术含量，但后面少了很多疑神疑鬼。要删东西时，先知道自己正在碰哪个卷。",[37,123,125],{"id":124},"uploads-空了文件列表还在","uploads 空了，文件列表还在",[10,127,128],{},"卡住我的其实是文件管理。",[10,130,131],{},"我先删了上传目录：",[133,134],"copy",{"code":135,"prompt":136},"rm -rf \u002Fvar\u002Flib\u002Fdocker\u002Fvolumes\u002Fopen-webui\u002F_data\u002Fuploads\u002F*","#",[10,138,139,140,142],{},"然后打开 Open WebUI，文件列表里还是一堆 ",[17,141,19],{"code":19},"。有 png，也有 gif，日期全是今天。",[10,144,145],{},"那一刻很容易怀疑自己删错了。于是开始查：",[45,147,152],{"className":148,"code":150,"language":151,"meta":51},[149],"language-bash","docker inspect openwebui --format '{{range .Mounts}}{{println .Name \"->\" .Destination}}{{end}}'\n","bash",[17,153,150],{"__ignoreMap":51},[10,155,156],{},"确认结果没问题：",[45,158,161],{"className":159,"code":160,"language":50,"meta":51},[48],"open-webui -> \u002Fapp\u002Fbackend\u002Fdata\n",[17,162,160],{"__ignoreMap":51},[10,164,165],{},"也就是说，容器确实用的是这个卷，路径也没错。问题不在目录。",[10,167,168,169,172,173,175],{},"后来才想明白：Open WebUI 的文件列表不是现扫 ",[17,170,171],{"code":171},"uploads\u002F"," 得来的。",[17,174,171],{"code":171}," 只是实体文件，UI 读的是数据库里的文件记录。文件没了，数据库还记着，界面当然继续显示。",[177,178,179,182,188,191,196,199],"timeline",{},[10,180,181],{},"{删目录}",[10,183,184,185,187],{},"删 ",[17,186,171],{"code":171},"，实体图片没了。",[10,189,190],{},"{查挂载}",[10,192,193,194,98],{},"查 volume，确认容器确实挂着 ",[17,195,93],{"code":93},[10,197,198],{},"{查数据库}",[10,200,201,202,98],{},"进正在运行的容器，直接查 ",[17,203,204],{"code":204},"\u002Fapp\u002Fbackend\u002Fdata\u002Fwebui.db",[10,206,207],{},"最后定位问题靠的是这条：",[45,209,212],{"className":210,"code":211,"language":151,"meta":51},[149],"docker exec -i openwebui python - \u003C\u003C'PY'\nimport sqlite3\n\ncon = sqlite3.connect(\"\u002Fapp\u002Fbackend\u002Fdata\u002Fwebui.db\")\ncur = con.cursor()\n\ncur.execute(\"SELECT COUNT(*) FROM file\")\nprint(\"file\", cur.fetchone()[0])\n\ncur.execute('SELECT id, filename, path FROM \"file\" ORDER BY created_at DESC LIMIT 5')\nfor row in cur.fetchall():\n    print(row)\n\ncon.close()\nPY\n",[17,213,211],{"__ignoreMap":51},[10,215,216,217,220,221,223],{},"输出里很直接，",[17,218,219],{"code":219},"file"," 表里就是那些 ",[17,222,19],{"code":19},"。路径也都指向：",[45,225,228],{"className":226,"code":227,"language":50,"meta":51},[48],"\u002Fapp\u002Fbackend\u002Fdata\u002Fuploads\u002Fxxx_generated-image.png\n",[17,229,227],{"__ignoreMap":51},[10,231,232,233,236,237,239],{},"到这里就不用再猜了。界面上的残留在 ",[17,234,235],{"code":235},"webui.db"," 的 ",[17,238,219],{"code":219}," 表里。",[37,241,242],{"id":242},"别再绕临时容器",[10,244,245,246,249,250,253],{},"我前面绕了几次临时容器，又是 ",[17,247,248],{"code":248},"main"," 镜像，又是 ",[17,251,252],{"code":252},"latest"," 镜像，还要手动挂 volume。能做，但没必要。",[10,255,256],{},"后面我换了思路：既然 UI 是当前这个容器跑出来的，那就进这个容器查库。它正在用哪个数据库，我就处理哪个数据库。",[45,258,261],{"className":259,"code":260,"language":151,"meta":51},[149],"docker exec -i openwebui python - \u003C\u003C'PY'\nimport os, shutil, sqlite3\n\ndb = \"\u002Fapp\u002Fbackend\u002Fdata\u002Fwebui.db\"\ncon = sqlite3.connect(db)\ncur = con.cursor()\ncur.execute(\"PRAGMA foreign_keys=OFF\")\n\nfor table in [\"channel_file\", \"chat_file\", \"knowledge_file\", \"document\", \"file\"]:\n    cur.execute(\"SELECT name FROM sqlite_master WHERE type='table' AND name=?\", (table,))\n    if cur.fetchone():\n        cur.execute(f'SELECT COUNT(*) FROM \"{table}\"')\n        print(\"before\", table, cur.fetchone()[0])\n        cur.execute(f'DELETE FROM \"{table}\"')\n        print(\"cleared\", table)\n\ncon.commit()\ncon.execute(\"VACUUM\")\ncon.close()\n\nfor p in [\"\u002Fapp\u002Fbackend\u002Fdata\u002Fuploads\", \"\u002Fapp\u002Fbackend\u002Fdata\u002Fvector_db\"]:\n    if os.path.isdir(p):\n        shutil.rmtree(p)\n    os.makedirs(p, exist_ok=True)\n    print(\"cleared\", p)\n\nprint(\"done\")\nPY\n",[17,262,260],{"__ignoreMap":51},[10,264,265],{},"然后重启：",[133,267],{"code":268,"prompt":136},"docker restart openwebui",[10,270,271],{},"这次再进文件管理，空了。",[100,273,276],{"title":274,"type":275},"为什么这条命令靠谱","tip",[10,277,278,279,281,282,284,285,98],{},"它不需要猜 volume，不需要新拉镜像，也不需要关心临时容器用 ",[17,280,248],{"code":248}," 还是 ",[17,283,252],{"code":252},"。它直接进当前正在跑的容器，处理当前容器正在用的 ",[17,286,204],{"code":204},[37,288,290],{"id":289},"cache-不能乱删","cache 不能乱删",[10,292,293],{},"文件清完以后，我又顺手踩了另一个坑。",[10,295,296,297,300],{},"我之前把 ",[17,298,299],{"code":299},"\u002Fapp\u002Fbackend\u002Fdata\u002Fcache"," 也清了。结果 Open WebUI 启动时开始报：",[45,302,305],{"className":303,"code":304,"language":50,"meta":51},[48],"Cannot find an appropriate cached snapshot folder\nNetwork is unreachable\nNo embedding model is loaded\n",[17,306,304],{"__ignoreMap":51},[10,308,309],{},"这不是文件管理残留，而是 RAG 的 embedding 模型缓存没了。",[10,311,312],{},"Open WebUI 默认会找这个模型：",[45,314,317],{"className":315,"code":316,"language":50,"meta":51},[48],"sentence-transformers\u002Fall-MiniLM-L6-v2\n",[17,318,316],{"__ignoreMap":51},[10,320,321],{},"我当时 compose 里配了：",[45,323,326],{"className":324,"code":325,"language":76,"meta":51},[73],"RAG_EMBEDDING_MODEL_AUTO_UPDATE=false\n",[17,327,325],{"__ignoreMap":51},[10,329,330],{},"一开始我以为这就是“不下载模型”。其实不是。它更接近“不自动更新模型”。本地缓存还在时，Open WebUI 可以直接用；缓存被删了，它还是得找模型。找不到，又连不上 HuggingFace，就启动失败。",[10,332,333],{},"最后是用镜像源重新下载了一次：",[45,335,338],{"className":336,"code":337,"language":151,"meta":51},[149],"docker run --rm \\\n  -e HF_ENDPOINT=https:\u002F\u002Fhf-mirror.com \\\n  -e HF_HUB_OFFLINE=0 \\\n  -v open-webui:\u002Fdata \\\n  --entrypoint python \\\n  ghcr.io\u002Fopen-webui\u002Fopen-webui:latest - \u003C\u003C'PY'\nfrom huggingface_hub import snapshot_download\n\nsnapshot_download(\n    repo_id=\"sentence-transformers\u002Fall-MiniLM-L6-v2\",\n    cache_dir=\"\u002Fdata\u002Fcache\u002Fembedding\u002Fmodels\",\n    local_files_only=False,\n)\n\nprint(\"done\")\nPY\n",[17,339,337],{"__ignoreMap":51},[10,341,342],{},"看到日志里出现这两行，我才放心：",[45,344,347],{"className":345,"code":346,"language":50,"meta":51},[48],"Loading weights: 100%\nStarted server process\n",[17,348,346],{"__ignoreMap":51},[10,350,351,352,355],{},"以后我不会再把 ",[17,353,354],{"code":354},"cache"," 当垃圾目录清了。",[100,357,360],{"title":358,"type":359},"以后只清这些","info",[10,361,362,363,366,367,370,371,373],{},"清文件时处理 ",[17,364,365],{"code":365},"uploads","、",[17,368,369],{"code":369},"vector_db"," 和数据库里的文件表就够了。",[17,372,354],{"code":354}," 里可能有 embedding 模型，删掉以后还要重新下载。",[37,375,376],{"id":376},"我为什么绕了一圈",[10,378,379],{},"回头看，问题不算复杂。我绕路，是因为一开始把几件事混在了一起。",[381,382,383],"card-list",{},[384,385,386,392,399,411,416],"ul",{},[387,388,389,391],"li",{},[17,390,171],{"code":171}," 里放的是实体文件。",[387,393,394,236,396,398],{},[17,395,235],{"code":235},[17,397,219],{"code":219}," 表决定文件管理里显示什么。",[387,400,401,366,404,366,407,410],{},[17,402,403],{"code":403},"chat_file",[17,405,406],{"code":406},"knowledge_file",[17,408,409],{"code":409},"channel_file"," 是关联表，也要一起清。",[387,412,413,415],{},[17,414,369],{"code":369}," 是知识库向量数据。",[387,417,418,420],{},[17,419,354],{"code":354}," 里可能有 RAG 模型，不该顺手删。",[10,422,423,424,427],{},"这次最有用的教训其实很土：能 ",[17,425,426],{"code":426},"docker exec"," 就别在外面猜。",[45,429,432],{"className":430,"code":431,"language":50,"meta":51},[48],"查挂载：docker inspect openwebui\n查文件表：docker exec openwebui python\n清记录：DELETE FROM file\n清实体：uploads\n清向量：vector_db\n保留模型：cache\n",[17,433,431],{"__ignoreMap":51},[10,435,436],{},"这些命令不优雅，但排障时优雅不重要。少猜一次路径，少拉一次不相关镜像，比什么都强。",[37,438,439],{"id":439},"留一份短记忆",[10,441,442],{},"最后我把配置写到本地：",[45,444,447],{"className":445,"code":446,"language":50,"meta":51},[48],"C:\\Users\\Mius\\Desktop\\项目1\\openwebui\\openwebui-config.md\n",[17,448,446],{"__ignoreMap":51},[10,450,451],{},"里面只记几行：",[45,453,456],{"className":454,"code":455,"language":50,"meta":51},[48],"容器名称：openwebui\n镜像：ghcr.io\u002Fopen-webui\u002Fopen-webui:latest\n端口：3000 -> 8080\u002Ftcp\nVolume：open-webui\n数据目录：\u002Fvar\u002Flib\u002Fdocker\u002Fvolumes\u002Fopen-webui\u002F_data\n",[17,457,455],{"__ignoreMap":51},[10,459,460],{},"这类东西写太多反而没用。以后真出问题，能马上看到容器名、卷名和数据目录就够了。",[30,462,464],{"icon":463},"tabler:database",[10,465,466],{},"今天这事像整理抽屉。文件本体是纸，数据库记录是索引卡。纸扔了，索引卡还在，柜台当然还说有货。",[10,468,469],{},"下次再清 Open WebUI，我会先看两处：容器到底挂着哪个 volume，UI 这块数据到底是从目录读，还是从数据库读。",[10,471,472,473,475],{},"至于 ",[17,474,354],{"code":354},"，以后我会单独看待。它不是垃圾桶，里面可能有模型。先确认，再动手。省时间，也省火气。",{"title":51,"searchDepth":477,"depth":477,"links":478},4,[479,481,482,483,484,485],{"id":39,"depth":480,"text":40},2,{"id":124,"depth":480,"text":125},{"id":242,"depth":480,"text":242},{"id":289,"depth":480,"text":290},{"id":376,"depth":480,"text":376},{"id":439,"depth":480,"text":439},[487],"折腾记录","2026-06-14 17:55:30","记录一次 Open WebUI Docker 数据清理：volume 改名、文件列表残留、RAG 模型缓存，还有我为什么最后改用 docker exec。",false,"md",{"slots":493},{},true,"\u002F2026\u002Fopenwebui-cleanup-journey",null,{"text":498,"minutes":499,"time":500,"words":501},"9 min read",8.045,482700,1609,{"title":5,"description":489},{"loc":495},"posts\u002F2026\u002Fopenwebui-cleanup-journey",[506,507,508,509,510],"Open WebUI","Docker","SQLite","RAG","运维","tech","2026-06-14 18:12:00","ebTeoj8CyZy7YkvaC7kc6h-tkW8MQzzQhdt3vzvhd-E",[515,496],{"title":516,"path":517,"stem":518,"date":519,"type":511,"children":-1},"域名走 Cloudflare 优选后，怎么配缓存规则","\u002F2026\u002Fcf-cache-rules","posts\u002F2026\u002Fcf-cache-rules","2026-05-16 12:56:25",1781452628132]