Dify 1.1.3到1.13.2跨代升级:零数据丢失与一键迁移实战 开头先说说这次升级的起因。我手上的 Dify 一直跑在 1.1.3部署在 Ubuntu 24.04 上因为工作流和知识库的用法比较固定加上平时也没出过什么大问题就一直没动它。直到有个新需求要用到 1.10 之后才有的多租户能力我一看版本号好家伙1.1.3 到 1.13.2中间隔着 100 多个版本。这种大版本跳跃最怕什么不是新功能不会用而是数据结构和环境变量配置早就变得妈都不认识了。直接拉最新代码一启动轻则知识库索引全废重则数据库迁移直接报错连登录都进不去。这篇文章就是把我这次实操下来的完整过程拆开揉碎了讲包含备份校验、环境适配、迁移脚本执行、启动验证以及最后如果真的出问题怎么回滚。我当时给自己定的两个硬性要求就是标题里写的零数据丢失外加如果以后要换服务器也得能一键迁移。所以整个升级方案不是简单地在原机器上覆盖代码而是按照可搬运、可回滚、可验证的标准来设计的。整个流程走完我在本地和一台新服务器上都各跑了一遍确认数据完整无误才换上生产环境。1. 升级前必须想清楚的三件事备份边界、版本差距和方案选型很多人升级 Dify 就三步git pull、docker compose up -d、完事。这在同一个小版本内大概率没问题但从 1.1.x 跳到 1.13.x这种操作等于让系统在运行状态下直接跨过几十次数据库结构变更风险完全不可控。我这次专门花了一个多小时做升级前规划把下面三件事想透了才动手。1.1 这一跳到底跨了多少结构性变化先说结论1.1.3 和 1.13.2 之间Dify 的架构已经有明显调整。我列几个升级前必须知道的关键差异工作流从实验性功能变成了正式能力节点类型和 DSL 结构都有变化。1.1.x 时代的工作流 DSL 到了 1.13 版本里可能存在兼容性差异需要启动后逐个验证。知识库的索引和检索逻辑改动较大尤其是分段、清洗和召回流程。旧版本的索引数据在升级后不一定能被新版本正确读取需要确认向量数据库里的 collection 结构是否兼容。模型供应商的凭据加密存储逻辑有变化。1.1.x 时代部分敏感信息存储在数据库中新版本对 SECRET_KEY 的依赖更强如果你在升级过程里换了 SECRET_KEY会导致模型供应商的 API Key 解密失败。社区版新增了多租户支持。这个功能虽然对单用户部署没有直接影响但它会改变数据库中的租户关联逻辑数据库迁移脚本会新增相关表结构和默认数据。插件机制逐步成形。1.13 的 docker-compose 里能看到 plugin_daemon 服务这是老版本没有的。如果从 1.1.3 直接升上来新增服务会自动创建但要注意网络配置是否能正常互通。前后端 API 的权限校验策略有变化。旧版本创建的管理员账号密码大概率没问题但访问令牌、WebApp 相关的签名机制可能换了算法。说白了这一跳的本质是一次跨代升级不能拿升小版本的心态来对待。1.2 备份边界到底哪些东西丢了会要命备份不是把整个部署目录 tar 一份就完事你得先搞清楚 Dify 的数据到底落在哪些位置。根据我的部署方式源码目录在/opt/dify/dify/docker关键数据包含这几类数据类别存储位置丢失后果环境变量配置docker/.env全部配置丢失凭据无法解密PostgreSQL 数据docker/volumes/dify/db/data用户、应用、工作流、知识库元数据全部丢失Redis 缓存数据docker/volumes/dify/redis会话缓存、部分任务状态丢失向量数据库 Weaviatedocker/volumes/weaviate知识库向量索引全部失效上传文件/OSS 存储docker/volumes/dify/storage文档、图片等文件丢失插件数据docker/volumes/dify/plugin插件配置和安装包丢失其中最容易忽略的是.env里的SECRET_KEY。这个值如果在升级过程中被重新生成轻则模型供应商的 Key 要重新填重则已加密存储的数据无法解密。所以完整备份方案是.env单独打包 整个docker/volumes目录打包 docker-compose.yaml和旧版本镜像标识一起保存。我当时是在停机前用这几条命令做备份的cd /opt/dify/dify/docker # 备份 .env单独放一份防止后面误覆盖 cp .env .env.bak.1.1.3 # 打包 volumes 目录注意不要带容器运行时的临时文件 tar -czf /data/backup/dify_volumes_1.1.3_$(date %Y%m%d).tar.gz volumes/ # 额外用 pg_dump 导出一份 PostgreSQL 逻辑备份这是最保险的逃生通道 docker compose exec db pg_dump -U postgres dify /data/backup/dify_pg_$(date %Y%m%d).sql提示如果你不是用官方 docker compose 部署而是 docker run 直接起的容器那 volume 的名字可能不同用docker volume ls确认后再备份。这里的原则是宁可多备份不能漏备份。有人可能会问既然 volumes 目录已经打包了为什么还要额外导出 PG 的逻辑备份我的回答是如果你要跨版本升级失败后回滚或者换了新服务器重新部署docker volume 的物理文件拷贝回来不一定能用尤其是 PostgreSQL 的数据文件对大版本升级非常敏感。逻辑备份虽然恢复后可能要重新初始化向量索引但至少用户和配置数据都在这是一个兜底方案。1.3 升级方案选型在原环境上升级还是迁到新服务器当时我面临两个选择一是在现有服务器上原地升级二是把数据迁到一台新服务器上。原服务器升级的好处是网络、域名、反向代理都不用动坏处是如果迁移脚本执行到一半失败回滚操作会比较紧张。新服务器部署的好处是风险隔离新版跑起来没问题再切流量坏处是如果你没有负载均衡或域名指向调整的权限切换过程会麻烦一些。我的建议是如果手头有多余的机器或者云服务器可以临时开一台优先选择新服务器部署最新版本 数据迁移这和标题里说的一键迁移其实是同一个逻辑——先在别的机器上验证新版能正常启动、数据完整再把生产流量切过去。如果只有一台机器那就老老实实走原地升级但备份必须做得足够扎实至少保证能恢复到升级前的状态。我这次实际上做了两遍先在一台备用服务器上模拟迁移验证流程没问题再回原机器执行正式升级。第二遍操作明显心里有底得多因为所有坑都在备用机上踩过一遍了。2. 环境适配与旧版本停机Ubuntu 24.04 上的 Docker 组件检查升级 Dify 之前先确认 Docker 环境本身没有坑。Ubuntu 24.04 默认的 apt 源里的 docker.io 版本其实不算太老但和 docker compose 的配合可能会出问题。这次升级前我专门检查了一遍环境发现几个容易被忽略的点写在这里供参考。2.1 Docker 引擎版本和 Compose 插件的兼容性Ubuntu 24.04 的官方 apt 源里 docker.io 的版本通常能到 24.x 或者 25.xdocker compose v2 插件一般也会自带。但我强烈建议使用 Docker 官方 apt 源因为 WSL 或者 Ubuntu 默认源里的 Docker 包更新滞后部分老版本 Docker 在处理新版 compose 文件的deploy字段和网络配置时会有不兼容现象。检查当前环境的命令docker version docker compose version如果你看到的 compose 版本是 v1也就是docker-compose --version显示 1.x那建议先升级到 v2。新版 Dify 的 docker-compose.yaml 用的是 Compose Specification 格式v1 解析器对某些字段理解有差异尤其是name和extends相关的配置。升级 Docker 引擎我这里不展开讲有一点务必记住升级 Docker 服务本身不会删除容器和 volume但执行完 upgrade 之后要重启 docker 服务否则 docker compose up 可能报容器名冲突或者网络找不到。2.2 docker-compose v1 与 v2 的执行差异如果你以前一直用docker-compose up -d管理 Dify升级后建议改成docker compose up -d中间没有横杠。两个版本在项目名处理上会有差异v2 默认把当前目录名作为项目名而 v1 会连带父目录一起算。Dify 官方脚本里写的都是docker compose如果你混用可能会创建一个新的网络和容器组导致端口冲突。我当时在备用机上就踩过这个坑用 v1 把老版本停掉再用 v2 启动新版本结果 Docker 认为是两个不同项目新容器启动时提示 80 端口被占用。排查了半天才发现是项目命名空间不同容器网络隔离导致端口看不到占用但 nginx 就是起不来。解决方案很简单统一用 v2并且指定项目名docker compose -p dify up -d这样不管在哪个目录下执行项目名始终是dify和以前用docker-compose管理时的容器归属逻辑一致。2.3 安全停止旧版本避免数据库写坏升级之前要优雅停机而不是直接重启容器。原因是两个第一API 容器和 Worker 容器可能在处理异步任务直接 kill 会导致数据库里留下半截写入第二新版启动时会执行数据库迁移alembic如果旧版本容器还在运行迁移过程中两个进程同时操作数据库表轻则锁表重则数据损坏。正确停机顺序cd /opt/dify/dify/docker # 先停 nginx避免新请求进来 docker compose stop nginx # 再停 worker让它处理完当前任务后退出 docker compose stop worker # 停掉 api、web、plugin_daemon 等业务容器 docker compose stop api web plugin_daemon # 最后停数据库和中间件 docker compose stop db redis sandbox ssrf_proxy weaviate注意这里不推荐直接docker compose down因为 down 会把容器删除虽然 volume 不会删但重新创建容器后旧容器的日志和网络信息会丢失。如果你需要回滚保留旧容器及其日志对排查问题很有用。当然如果磁盘空间紧张可以用 down但记得备份日志。如果你的 Dify 版本里没有plugin_daemon或ssrf_proxy服务用docker compose ps看一下实际服务列表再停。停完之后等个 30 秒确认没有进程还在写数据再进入下一步。3. 迁移脚本的全流程拆解从旧 env 到新数据结构的对齐这章是整个升级过程的核心。Dify 官方其实提供了两个迁移脚本migrate_env_file.sh和migrate_data.sh但由于版本跨度太大我强烈建议先手动理解脚本干了什么再执行避免脚本因为旧版本结构太老而中途失败你却看不懂报错。3.1 为什么要先处理 .env环境变量版本对齐从 1.1.3 升级到 1.13.2.env里新增了很多配置项。如果你直接用旧.env启动新版本代码读到缺失的配置项时会采用默认值。大多数情况下没问题但有几个配置项必须保持和旧版本一致否则数据读取会出问题SECRET_KEY绝对不能变。如果变了数据库里已加密的模型供应商凭据和新版本生成的签名都会对不上。DB_DATABASE、DB_USERNAME、DB_PASSWORD必须和旧版本一致。你迁移过来的是旧 volume数据库账号密码变了会导致连不上。VECTOR_STORE如果你原来用的是weaviate千万别改成qdrant或者milvus。向量数据库类型一变系统会认为没有知识库索引让你重新初始化等于全部知识库要重建。STORAGE_TYPE原来用本地存储的保持本地原来用 S3/OSS 的保持远端配置。这个变了会导致上传文件找不回来。MIGRATION_ENABLED需要确认设置为true1.13 版本的 api 容器启动时会自动执行数据库迁移如果这个值被设为 falseschema 不会更新服务会报表不存在之类的错。官方脚本migrate_env_file.sh会把旧.env备份一份再合并新模板中的配置项。但这个脚本对极老版本的支持不一定完美我的建议是执行完脚本后手动打开.env检查一遍重点看上面列的几个字段是否保持原值。执行命令cd /opt/dify/dify/docker # 脚本会生成 .env 的备份再合入新版本模板 ./migrate_env_file.sh # 检查关键字段 grep -E ^(SECRET_KEY|DB_DATABASE|DB_USERNAME|VECTOR_STORE|STORAGE_TYPE|MIGRATION_ENABLED) .env如果检查发现SECRET_KEY被脚本覆盖成了新值马上把备份里的原值恢复回去。这个值是数据解密的关键新旧不一致会导致整个升级后模型供应商全部掉线。3.2 数据真正的落盘位置和迁移脚本做了什么Dify 的所有数据都挂在 docker volume 里。注意不是 Docker 管理的 anonymous volume而是 bind mount 到部署目录下的volumes/文件夹。这也是为什么 一键迁移 能成立——只要把这个目录和.env搬到新机器再补上代码和 compose 文件理论上就能恢复。migrate_data.sh脚本做的事情说直白点就是针对那些跨版本后存储结构发生变化的组件做一次原地数据转换。具体包括把旧的docker/volumes/dify目录结构迁移到新版预期的目录结构比如某些子目录从dify/storage变成了dify/storage/upload脚本会负责建立对应关系。对数据库执行一次温和的预迁移校验旧 schema 和新 schema 的差异。更新 Weaviate 等向量数据库的 schema 或索引策略。但对 1.1.3 到 1.13.2 这种大跨度我的经验是脚本可能会跑得比较吃力因为中间有些中间版本的迁移逻辑是被跳过的。此时最稳妥的方式不是直接跑脚本而是先把整个docker/volumes目录原封不动复制到新版本部署目录下或者原地保留。手动确认 compose 文件中的 volume 映射路径与旧版本完全一致。然后启动新版让 api 容器启动时自动执行 alembic 数据库迁移。1.13 版本的migrate_data.sh如果报错再根据报错内容决定是否需要手动处理中间版本数据结构。执行迁移脚本cd /opt/dify/dify/docker ./migrate_data.sh如果脚本执行成功会输出类似Migration completed的提示。如果失败也不要慌先把输出日志保存下来回到 3.1 检查.env再检查 volumes 目录权限。最常见的失败原因是 volumes 目录的所有者变了导致容器内进程没有写权限。Ubuntu 上如果目录所有者是 root而容器里跑的是普通用户迁移脚本就写不进去。此时用一条命令解决chown -R 1000:1000 /opt/dify/dify/docker/volumes为什么要用 1000:1000因为 Dify 容器内默认用户 UID 是 1000对应容器内叫dify的用户。你直接在宿主机上改成 root 反而会导致容器内无法读取。3.3 一键迁移场景把整站搬到新服务器的关键差异如果你的目标不只是原地升级而是像标题里说的一键迁移——把旧机器上的 Dify 搬到新服务器那要注意的方式和原地升级略有不同。我建议的迁移清单是源目标说明旧机器docker/.env新机器docker/.env手动复制不要用脚本生成新值旧机器docker/volumes/新机器docker/volumes/直接 rsync 或 tar 打包传输旧机器docker/docker-compose.yaml新机器对应文件可保留旧版也可以替换为新版旧机器 nginx 配置如有自定义新机器 nginx 配置证书和域名相关需要调整传输大目录用 rsync 比 tar 更方便因为可以断点续传rsync -avP /opt/dify/dify/docker/volumes/ usernew-server:/opt/dify/dify/docker/volumes/到了新服务器之后你需要用新版代码启动而不是直接起旧版。换句话说迁移数据的时机是数据目录就位之后容器启动之前——所有数据先搬过去再让新版代码做升级迁移。这和原地升级的操作顺序本质相同只是数据和代码在不同机器上而已。如果新旧服务器的磁盘目录结构不一致比如原来/opt/dify在新机器上变成了/srv/dify记得打开 docker-compose.yaml 检查所有:/app之类路径映射是否正确。路径没关系只要.env中的DIFY_BIND_ADDRESS等网络配置和 compose 的端口映射保持一致就行。4. 新版启动与零丢失校验清单迁移脚本跑完只是完成了准备阶段真正的考验在你敲下docker compose up -d之后。新容器启动时会自动执行数据库迁移、初始化插件、重建索引等操作整个过程可能出现各种各样的情况。这里我按启动后的检查顺序写一个校验清单你可以照着一步一步来。4.1 启动顺序与日志观察启动新版 Dify 的时候我建议先启动依赖服务再启动业务服务可以避免因为依赖未就绪导致 api 或 web 容器重启好多次。执行命令cd /opt/dify/dify/docker # 先拉起中间件 docker compose up -d db redis sandbox ssrf_proxy weaviate # 等待 30 秒确认数据库等组件 healthy docker compose ps # 再拉起业务服务 docker compose up -d api worker web plugin_daemon # 最后挂上 nginx 入口 docker compose up -d nginx观察容器状态docker compose ps docker compose logs -f api在 api 日志里你可能会看到类似Running migration: xxx、Alembic upgrade head之类的输出。这说明数据库迁移正在执行请耐心等它走完。迁移过程中如果报错日志里会明确告诉你是哪张表、哪个约束出了问题保存日志后再处理。4.2 数据完整性验证用户、应用、知识库、工作流容器都起来之后浏览器打开 Dify 登录页先用管理员账号登录。第一步不是急着创建新应用而是进入后台逐个检查旧数据用户列表是否完整管理员账号是否正常密码没有被重置。如果你登录失败先用docker compose logs api看日志大多数情况下是 SECRET_KEY 不对导致的加密内容无法解密。应用列表里的旧应用是否都在每个应用的编排模式工作流/对话流/Agent是否正常打开。知识库列表是否完整每个知识库的文档数量是否为 0。如果文档数是 0检查 Weaviate 容器是否正常以及.env里VECTOR_STORE是不是还指向 weaviate。工作流是否能正常打开。1.1.3 时代的 DSL 在新版本里如果提示格式错误不要慌新版本有兼容模式点击保存一次会自动转换成新版结构。模型供应商列表里已配置的 API Key 是否还在。如果显示为空或提示解密失败就是 SECRET_KEY 没有对齐马上停止改动拿备份重新恢复。注意启动后首次加载知识库列表时新版本可能会触发一次索引同步如果数据量很大建议先别急着测试让 worker 容器安静跑一会儿等日志里不再出现Indexing相关输出再操作。4.3 从数据校验到业务验证数据完整性检查没问题后建议跑几条真实链路测试新建一个临时应用走一遍对话-知识库检索流程确认知识库召回能正常返回内容。如果召回结果异常优先检查向量库的 collection 数和文档 chunks 数是否一致。在工作流里触发一次 HTTP 请求节点或代码节点确认沙箱运行环境正常。用不同浏览器/用户身份各登录一次确认多租户隔离没有影响旧应用权限。这一轮跑完之后再清理临时应用和测试数据零数据丢失的目标就算达成了。5. 排错与回滚升级中遇到的实际问题和逃生通道即使前面准备得再充分升级过程中还是有可能遇到意外。这一章把我在整个过程中撞到的几个主要问题以及对应的排查思路和回滚方案都写出来希望能帮你少走弯路。5.1 跨版本升级中常见的报错与根因我这次在备用机和原机上遇到的报错不算多但每一个都很有代表性整理成表格供排查时对照报错现象根因处理方式ModuleNotFoundError: No module named diffusers或类似 Python 包缺失镜像版本没有完整拉取最新或本地缓存了旧镜像docker compose pull重新拉取确认使用dify-api:1.13.2标签database dify does not exist数据库 volume 路径不对或.env中 DB 配置被改检查 compose 文件和.env确认映射到volumes/dify/db/datarelation xxx does not exist数据库迁移没有执行成功查看 api 容器日志确认 alembic 是否跑完手动执行docker compose exec api flask db upgrade如果存在知识库文档数为 0向量数据库连接不上或 VECTOR_STORE 配置错误检查 weaviate 容器健康状态和.env配置登录后提示SECRET_KEY错误.env中的 SECRET_KEY 不一致恢复备份.env重启容器plugin_daemon容器反复重启插件数据目录权限不对确认volumes/dify/plugin目录权限参考 3.2 中的 chown 操作nginx 503web 容器未就绪等待 web 容器启动完成查看 web 日志这中间最折磨人的是数据库迁移报错。比如迁移脚本在中途对一个已经存在的表执行新增约束时失败通常是因为旧版本的数据里有重复记录。此时不要尝试手动改数据库正确做法是把报错信息截图保存然后恢复到备份状态用更稳妥的方式处理比如在升级前先手动清理脏数据。5.2 一键回滚操作如何安全复原到 1.1.3升级失败不可怕可怕的是没有想好回滚路径。我在升级前就把旧版本的所有备份按可独立恢复的标准整理好了回滚方案分两层。第一层如果只是容器启动失败但 volume 数据没有被破坏回滚很简单把.env恢复成备份版本把 docker-compose.yaml 恢复成旧版然后启动旧容器。由于容器镜像还在本地可以用docker compose up -d直接拉起来。cd /opt/dify/dify/docker # 恢复 .env cp .env.bak.1.1.3 .env # 恢复旧版 compose 文件如果升级时覆盖了的话 # git checkout 或手动替换回旧文件 # 启动旧容器 docker compose up -d注意如果新版 api 容器启动时已经执行了数据库迁移旧版代码再启动时可能因为 schema 已经变成新版而报错。这种情况下只能用第二层回滚——从 volume tar 包完整恢复。第二层完整恢复 volume 数据。执行流程# 停止所有容器 cd /opt/dify/dify/docker docker compose down # 删除被改动过的 volumes 目录 rm -rf volumes/ # 从备份 tar 包解压恢复 tar -xzf /data/backup/dify_volumes_1.1.3_$(date %Y%m%d).tar.gz -C volumes/ # 恢复 .env cp .env.bak.1.1.3 .env # 启动旧容器 docker compose up -d如果 volumes tar 包恢复后数据库起不来就用之前导出的dify_pg_*.sql恢复数据库。这层回滚的本质是回到升级前的物理状态所以耗时取决于数据量大小但逻辑上没有任何不可逆的风险。我把这套操作在备用机上完整演练了一遍确认可行才动手正式升级。5.3 大版本升级的几条经验总结最后说几条大版本升级的通用经验不是针对 Dify而是这一类跨代升级都适用的原则。第一永远不要在升级前删旧版本镜像。Docker 镜像占磁盘空间确实多但旧镜像留着是回滚时最省事的办法——container 的配置可以改镜像没了就只能重新 pull 旧 tag如果 Docker Hub 上旧 tag 被覆盖或者下线你就彻底失去了一键回滚的能力。第二升级过程不要关终端。我见过有人在执行迁移脚本或者 docker compose up 的时候因为输出太久没有新内容以为卡住了就 CtrlC 中断。实际上容器内数据库迁移对大库来说跑个十几分钟很正常。判断是否卡住用docker stats看容器 CPU 和内存是否还在变化而不是凭感觉。第三升级前一定要在另一台机器上验证过迁移流程再碰生产。没有条件开新机器的话至少把数据 tar 包复制到本地用 Docker Desktop 或者虚拟机跑一遍完整的新版本 旧数据启动流程。这套验证流程虽然要额外花时间但它是把生产事故消灭在发生之前的唯一可靠手段。第四把.env的备份放在部署目录以外的地方。目录内的备份如果因为误操作被覆盖连退路都没了。我习惯在/data/backup下按日期建目录把.env.bak、docker-compose.yaml.bak、tar.gz放在一起文件名里带上版本号和日期这样即使过了一个月也能一眼看出哪个备份对应哪个版本。这次升级从 1.1.3 到 1.13.2我实际花的完整时间为备份 30 分钟备用机验证 2 小时正式升级 40 分钟回滚演练 20 分钟。如果把这些准备时间都算进升级过程里可能很多人会觉得太慢了。但换个角度想如果直接硬升级导致数据丢失恢复数据的时间成本按小时计而且还未必能找回全部内容。到底哪个更划算这笔账我相信你心里有数。