首页
/ Dify 跨环境应用迁移实战:用迁移包在工作区之间搬运 Workflow、Chatflow 与自定义工具

Dify 跨环境应用迁移实战:用迁移包在工作区之间搬运 Workflow、Chatflow 与自定义工具

2026-09-04 23:48:54作者:段琳惟

当团队把 Dify 从测试环境推向生产环境(或从一套自托管集群迁移到另一套),Workflow 应用、Chatflow 应用及其依赖的自定义工具往往无法仅靠 DSL 文件简单复制。本文基于 Dify 仓库的跨环境迁移指南与配套源码,完整讲解如何用“迁移包(migration package)”把应用与工具依赖从一个环境搬到另一个:包括推荐的向导式导出 + 导入流程、可选的脚本化自动导出、全部命令行参数、导出配置字段、引用工具自动发现机制、密钥(secrets)处理策略与导入冲突策略的取舍。读完本文,你可以独立执行一次完整的跨环境迁移,看懂导入报告中的每一项状态,并从源码层面理解 ID 映射与引用重写的实现原理。

迁移包能带走什么

迁移包(JSON 文件)可以包含以下资源(参见 docs/cross-env-app-migration/README.md 的 “What Gets Migrated” 一节):

  • Workflow 应用与 advanced-chat(Chatflow)应用:以 DSL 内容形式写入包的 workflows 分区;
  • 自定义 API 工具提供者(Custom API tool providers):写入 tools 分区;
  • Workflow 工具提供者(Workflow tool providers):即把某个 Workflow 应用发布为工具,写入 workflow_tools 分区;
  • MCP 工具提供者:仅在显式开启 include_secrets 时以完整提供者数据导出,否则仅记录为依赖元数据,写入 mcp_toolsdependencies 分区;
  • 依赖元数据:内置工具(built-in tools)与插件工具(plugin tools)不会被序列化为迁移数据,而是记录为“必须已在目标环境中存在或配置好”的依赖项,写入 dependencies 分区。

两条硬性约束:

  1. 仅支持单源工作区(single source workspace)导出。包的 metadata.source_scope 必须为 single,源码中 MigrationMetadata.from_mapping 遇到其他取值会直接抛出 MigrationDataErrorentities.py)。
  2. 源与目标工作区名称不需要一致--target-tenant 允许在导入时指向与源不同的工作区,这正是该机制支持“staging → production 镜像迁移”的关键。

从源码结构看,迁移包的完整形状由 MigrationPackage 定义,包含 metadata(版本、源工作区、可选目标选择器、include_secretsimport_options)以及 workflowstoolsworkflow_toolsmcp_toolsdependencies 五个列表分区。包版本常量固定在 package_service.pyPACKAGE_VERSION = "1",导入时 load_package 会校验版本,遇到不支持的版本直接报错,因此不同大版本之间不保证兼容。

推荐流程:向导导出 + 导入

官方推荐的迁移路径是(来自文档 “Recommended Flow” 一节):

  1. 源环境运行导出向导;
  2. 目标环境导入生成的迁移包。

向导方式之所以被推荐,是因为它会列出可用应用与工具、自动发现应用依赖、在写盘前打印摘要(summary),并且与脚本化导出共用同一个导出服务 MigrationExportService,两条路径产出的包结构一致。

第一步:运行导出向导(源环境)

cd api
source .venv/bin/activate
uv run flask app-migration-wizard

该命令没有 CLI 参数,全部通过交互式输入完成。对应源码是 data_migration.py@click.command("app-migration-wizard") 装饰的 migration_data_wizard 函数。按源码实现,向导依次询问以下内容:

交互项 说明 默认值
Source tenant 选择源工作区,从按名称排序的租户列表中按编号选择一个 1
App selection 选择要导出的应用;仅列出 workflowadvanced-chat 两种模式的应用(SUPPORTED_WIZARD_APP_MODES)。输入 all、单个编号或逗号分隔的编号 all
Automatically export tools referenced by selected apps? 自动发现所选应用引用到的工具,推荐选择 yes。向导会导出每个应用的 DSL(AppDslService.export_dsl)并扫描其中的工具节点与 agent 工具配置,自动纳入引用的自定义 API 工具、workflow 工具与 MCP 工具引用 y
Export additional tools manually? 手动追加未被所选应用引用的工具(三类分别列出,已自动发现的条目会带 [auto] 标记) n
Include secrets in output JSON? 是否输出密钥。no 时:workflow/app DSL 密钥被省略或脱敏、API 工具凭据被剔除、MCP 提供者仅以依赖元数据记录;yes 时:输出 JSON 应视为敏感文件 n
Create or reuse app API tokens during import? 导入时为没有 API token 的已导入应用创建 token,或复用已有 token n
Import ID strategy preserve-idgenerate-new-id preserve-id
Import conflict strategy failskipupdate(注意:向导默认是 update,而包与脚本模板默认是 fail update
Output path 输出路径,默认 migration-data-YYYYMMDD-HHMMSS.json 时间戳文件名
Overwrite existing output 仅当输出文件已存在时才询问 n
Write migration package? 打印摘要后的最终确认,选择 n 会以 click.Abort 中止且不写盘 y

向导最后一步在写盘前调用 _confirm_wizard_summary 打印源租户、选中应用清单、工具选择([auto]/[manual] 分类)、include secrets、ID/冲突策略与输出路径,确认后才真正调用导出服务并写文件,然后渲染导出报告。

第二步:导入迁移包(目标环境)

把生成的 JSON 包复制到目标环境后执行:

cd api
source .venv/bin/activate
uv run flask import-app-migration \
  --input migration-data-20260528-120000.json \
  --target-tenant "production Workspace"

完整选项(对应源码 import_migration_data):

  • --input必填):迁移包 JSON 路径,可以是向导或脚本化导出生成的包;
  • --target-tenant:目标工作区名称或 UUID。仅当包元数据已包含 target_tenant 时可省略。CLI 值覆盖包元数据,官方建议对“可复用的包”始终显式传入;
  • --operator-email:目标工作区内作为导入操作者的账号邮箱。省略时,导入服务自动选择目标租户中最早创建的 owner 账号ImportTargetResolver.resolve);
  • --id-strategy:可选覆盖包内 import_options.id_strategy,取值:
    • preserve-id:在目标服务支持时保留源应用/工具 ID,是跨环境迁移的推荐默认,便于保持 workflow 引用的稳定;
    • generate-new-id:由目标环境生成新 ID,并通过迁移 ID 映射(ID mapping)重写引用;
  • --conflict-strategy:可选覆盖包内冲突策略,取值:
    • fail:遇到第一个已存在的目标资源冲突即停止,已提交的资源不会回滚
    • skip:保留目标已有资源,跳过该资源的导入;
    • update:就地更新目标已有资源;
  • --create-app-api-token-on-import / --no-create-app-api-token-on-import:可选覆盖导入时的应用 API token 创建行为。

这些覆盖选项的合并逻辑见 _build_options_override:只有显式传入的项才覆盖包内 import_options,未传入的项沿用包内默认值。

导入完成后会打印一份报告(由 MigrationReportService 渲染),内容包括:解析出的目标租户与操作者、各资源的 created / updated / skipped / unresolved / dependency-only 状态、未解析(unresolved)依赖、应用 API token 的创建/复用数量,以及用于重写引用的 ID 映射表。报告中凡是标记为 dependency-onlyskippedunresolved 的条目,通常都需要人工跟进。

可选流程:脚本化导出(用于可重复自动化)

当你需要可重复、可纳入 CI 或发布流水线的导出时,官方给出四步脚本化路径(来自文档 “Optional Flow” 一节):

  1. 生成导出配置模板;
  2. 编辑模板;
  3. 运行脚本化导出;
  4. 导入生成的包。

模板命令

cd api
source .venv/bin/activate
uv run flask app-migration-template --output export-config.json

选项(对应 export_migration_data_template):

  • --output(可选):写出模板 JSON 的路径;省略时模板打印到 stdout;
  • --overwrite(可选):允许替换已存在的输出文件。不带该选项且 --output 已存在时命令直接失败。

脚本化导出命令

cd api
source .venv/bin/activate
uv run flask export-app-migration \
  --input export-config.json \
  --output migration-package.json

选项:

  • --input(必填):导出配置 JSON 路径;
  • --output(必填):迁移包 JSON 输出路径;
  • --overwrite(可选):允许替换已存在的输出包,否则已存在即失败。

随后用与前面完全相同的 import-app-migration 命令导入该包。

导出配置字段详解

app-migration-template 生成的模板形状如下(模板构造函数见 _scripted_export_template):

{
  "source_tenant": {
    "mode": "single",
    "id": "",
    "name": "admin's Workspace"
  },
  "apps": {
    "modes": ["workflow", "advanced-chat"],
    "ids": [],
    "all": true
  },
  "include_referenced_tools": true,
  "additional_tools": {
    "api_tools": [],
    "workflow_tools": [],
    "mcp_tools": []
  },
  "include_secrets": false,
  "import_options": {
    "create_app_api_token_on_import": false,
    "id_strategy": "preserve-id",
    "conflict_strategy": "fail"
  }
}

各字段含义(解析逻辑见 ExportConfigParser):

字段 说明
source_tenant.mode 必须为 single,其他取值报错
source_tenant.id 可选的源工作区 UUID。当存在同名工作区、或希望做严格源选择时填写。若设置了 id,则 ID 与 name 必须匹配,否则导出失败(MigrationExportService._get_tenant 会做一致性校验)
source_tenant.name 必填的源工作区名称。仅按名称查询且命中多个同名工作区时,导出会报 “name is ambiguous”,提示改用 source_tenant.id
apps.modes 可选的应用模式校验列表,仅支持 workflowadvanced-chat,出现其他值报错
apps.ids apps.allfalse 时,指定要导出的应用 ID 列表。列出的 ID 若不存在或不属于受支持模式,会明确报出缺失的 ID
apps.all true 时导出所选源工作区内的全部受支持应用;false 时仅导出 apps.ids
include_referenced_tools 推荐 true。自动发现所选 workflow/chatflow 应用引用的工具
additional_tools.api_tools 追加导出的自定义 API 工具提供者名称列表
additional_tools.workflow_tools 追加导出的 workflow 工具提供者 ID 列表
additional_tools.mcp_tools 追加导出的 MCP 提供者 ID 或 server identifier 列表
include_secrets 默认 false。为 false 时凭据被剔除、MCP 提供者仅记为依赖元数据;为 true 时自定义 API 凭据、workflow/app DSL 密钥值、完整 MCP 连接数据会写入包
import_options.create_app_api_token_on_import 默认 false,包级别的导入 token 创建默认值
import_options.id_strategy 包级别默认 ID 策略:preserve-id / generate-new-id
import_options.conflict_strategy 包级别默认冲突策略:fail / skip / update

从源码结构看,ExportConfigParser.parse 还兼容若干旧字段名:apps.ids 缺省时回退读顶层 workflowsexport_all_apps 回退读 export_all_workflows、各类工具列表回退读顶层 tools / workflow_tools / mcp_toolsexport_service.py),这属于对早期包结构的向后兼容路径,新配置建议统一使用模板字段。

引用工具自动发现:向导与脚本共用同一机制

文档 “Referenced Tools” 一节的要点是:尽量使用自动引用工具发现。开启后,Dify 会扫描所选 workflow/chatflow 的图(graph)与 agent 工具配置,对发现的自定义 API 工具、workflow 工具、MCP 工具引用去重后再导出,从而降低“导入的应用引用了不存在的 provider”的概率。

对应实现是 DependencyDiscoveryService

  • 它遍历 DSL 中 graph.nodesworkflow.graph.nodes 两类节点布局;
  • tool 类型节点读取节点 data 中的工具配置,对 agent 类型节点读取 tools 列表或 agent_parameters.tools.value 中的每个工具配置(dependency_discovery_service.py);
  • provider_type 归类:api/custom/api_tool → API 工具;workflow/workflow_tool → workflow 工具;mcp → MCP 工具;其余一律归为 builtin_or_plugin_tool _kind_from_provider_type),并按 (kind, provider_id) 去重。

两个重要的边界行为:

  1. 内置工具与插件工具永不被序列化为迁移数据。它们只作为依赖元数据写入包的 dependencies 分区,报告状态为 dependency-only,提示词来自 _dependency_message:“Ensure the built-in or plugin tool exists in the target environment.” 你必须确保目标环境已安装并配置好这些工具。
  2. MCP 提供者同样是 dependency-only(除非 include_secrets=true)。若未导出 MCP 密钥,需要在目标工作区手动配置对应 MCP 提供者,再运行迁移后的 workflow。导入阶段还会做前置检查: _preflight_dependency_only_mcp 会在目标租户中查找每个依赖型 MCP 提供者,找到则报 available,找不到则报 skipped 并列出引用它的具体 workflow 与工具节点,明确提示 “configure it manually before running the workflow.”

另外,向导在发现工具后还会做名称解析:_discover_auto_toolsinclude_secret=False 的 DSL 导出触发发现,随后 _resolve_auto_tool_names 按租户内 provider 表把发现结果解析为“名称 + ID”,MCP 工具则支持 server_identifier 匹配,保证摘要打印出的都是可读的 provider 名称。

密钥(Secrets)处理

安全默认值是 include_secrets: false。只有在你拥有受控的 JSON 包传输通道时才应开启密钥导出。从源码看,include_secrets=false 的具体行为包括:

  • 自定义 API 工具 _export_api_toolsmask=True 调用 ToolManager.user_get_api_provider 并显式 pop("credentials"),即只导出 provider schema(参数结构、认证类型等),不含任何凭据值;
  • Workflow/app DSLAppDslService.export_dsl(include_secret=selection.include_secrets)false 时 DSL 中的密钥类值被省略或脱敏;
  • MCP 提供者:走 _record_dependency_metadata 分支,只记依赖;开启 include_secrets=true 时则通过 _serialize_mcp_provider 写出解密后的 server_urlheadersauthentication(client_id/client_secret)、超时配置与缓存的工具列表。

因此,启用 include_secrets: true 后,包中可能包含 API 工具凭据、workflow/app DSL 密钥值、MCP server URL、MCP headers、认证数据与缓存的 MCP 工具列表——该 JSON 应按机密文件对待。

FAQ(继承自官方文档)

问:include_secrets=false 时会发生什么?

这是推荐默认值,但它意味着迁移包对敏感运行时配置是故意不完整的:

  • MCP 工具不会作为完整工具提供者导出,仅记录为依赖元数据;
  • 自定义 API 工具凭据不导出。Provider schema 可以导出,但凭据必须在目标工作区重新配置;
  • Workflow/app DSL 密钥被省略或脱敏。导入后需要检查目标工作区中的每个应用变量、凭据与密钥相关配置;
  • 内置与插件工具永远不作为自定义迁移数据序列化,仅记为依赖。

处理办法:

  • 导入前,先在目标环境安装/启用所需的内置或插件工具;
  • 对 MCP 工具,先在目标工作区手动创建或配置对应 MCP 提供者,再运行迁移后的 workflow;
  • 对自定义 API 工具,在目标工作区打开导入后的 provider 重新填写凭据;
  • 导入后、投产前逐个复查每个迁移的 workflow/chatflow,重点关注工具节点、agent 工具配置、环境变量、应用变量与凭据类设置;
  • 使用导入报告:标记为 dependency-onlyskippedunresolved 的条目通常需要人工跟进。

如果你确实需要包携带完整 MCP 配置与凭据,把 include_secrets 设为 true 重新导出,并将生成的 JSON 作为敏感数据传输。

问:include_secrets=true 时需要考虑什么?

包中可能包含上述全部敏感运行时配置。处理建议:

  • 把 JSON 包按机密存储与传输;
  • 若安全策略要求,导入完成后删除包文件;
  • 优先只用于“受控的一次性迁移”场景——即目标侧手工配置凭据的成本高于保护包成本的情形。

问:应该用 preserve-id 还是 generate-new-id

ID 策略控制导入的资源是保留源 ID 还是在目标环境获得新 ID,适用于目标服务支持显式 ID 的 workflow 应用与自定义工具资源(策略枚举见 entities.py)。

默认用 preserve-id,推荐用于跨环境应用迁移:导入的应用与工具尽量保留源 ID,workflow 引用更容易保持原样。适用场景:

  • 两个环境本应互为镜像(如 staging → production);
  • 你希望 workflow 工具引用、provider 引用尽可能稳定;
  • 目标环境中不存在与源 ID 相同但无关的资源。

需要注意:目标已存在同 ID 资源时,fail 会停止导入、skip 保留目标资源、update 就地更新;如果该 ID 在目标环境被复用于不同资源,使用 update 前务必仔细核对。

generate-new-id 的场景:目标环境应保留自己的本地 ID;例如目标已有可能与源 ID 冲突的资源、你是把包作为“副本”而非“镜像”导入、或不想在目标环境保留源数据库 ID。需要注意:

  • 导入会记录源到目标的 ID 映射,务必复查导入报告中的 ID mappings;
  • Workflow DSL 中的 provider 引用会尽可能通过该映射被重写(实现见 _rewrite_workflow_dsl_provider_ids,它会遍历 graph.nodesworkflow.graph.nodesdata.provider_id 以及 agent 工具配置);
  • 依赖元数据型资源(如 include_secrets=false 导出的 MCP 提供者)仍需在目标侧手工配置;
  • 内置/插件工具不会被重映射为迁移的自定义资源,目标环境必须自行提供。

导入执行顺序与源码级细节

理解以下执行细节,能帮助你预测导入报告的内容与失败点。核心实现在 MigrationImportService.import_package,其顺序是刻意为之的依赖拓扑序:

  1. 解析导入目标(先于任何写入):ImportTargetResolver 按 “CLI --target-tenant > 包元数据 target_tenant” 的顺序解析目标工作区,支持 UUID 或名称;名称存在歧义(多个同名工作区)时直接报错(import_service.py)。操作者账号未指定时取最早创建的 owner。
  2. 导入 API 工具package.tools):按 provider 名称查目标租户中的现有提供者;不存在则经 ApiToolManageService.create_api_tool_provider 创建,存在且 conflict_strategy=update 则调用 update_api_tool_provider 就地更新。注意 API 工具的“已存在”判定按名称而非 ID,preserve-id 并不直接写入源 UUID,目标侧记录映射。
  3. 导入 MCP 工具package.mcp_tools,仅含密钥时非空):按 server_identifier(或 UUID)查现有提供者,创建或更新;更新时会保留目标已存储的 identity_mode,避免静默重置转发行为(import_service.py),并把包中缓存的工具列表写回 provider 且标记 authed=True_restore_mcp_provider_tools)。
  4. MCP 依赖前置检查:对依赖型 MCP 条目输出 available / skipped 报告。
  5. 先导入 workflow 工具引用的源应用:如果包的 workflow_tools 引用了尚未导入的应用,会先把这些“被引用的 workflow 应用”单独导入(only_app_ids 分支),且导入 workflow 工具前会确保其宿主应用已发布——必要时自动调用 WorkflowService.publish_workflow 以 “Migration import” 名义发布( _ensure_workflow_app_is_published)。
  6. 导入 workflow 工具WorkflowToolManageService.create_workflow_tool / update_workflow_tool)。
  7. 导入其余应用(跳过第 5 步已导入的),每个应用通过 AppDslService.import_appyaml-content 模式导入 DSL;preserve-id 且应用不存在时以源 app_id 作为 import_app_id 建应用,存在且策略为 update 时以现有应用 ID 覆盖导入;fail 策略下遇到已存在应用会抛出 MigrationDataErrorimport_service.py)。
  8. 应用 API token:开启时经 _create_or_reuse_app_api_token,已有 APP 类型 token 则复用,否则用 ApiToken.generate_api_key("app", 24) 新建。

再次强调 conflict_strategy=fail 的语义:在第一个冲突处停止,之前已提交(committed)的资源不会回滚。因此对不确定的目标环境,建议先用 fail 试跑看报告,再决定用 skipupdate 正式执行;也可以先用 fail 探测、通过 --conflict-strategy 覆盖包内默认值复跑。

相关源码与测试入口

快速参考

场景 命令
交互式迁移(推荐) 源环境 uv run flask app-migration-wizard → 目标环境 uv run flask import-app-migration --input <包> --target-tenant "<工作区>"
生成脚本化导出配置 uv run flask app-migration-template --output export-config.json
可重复自动化导出 编辑配置后 uv run flask export-app-migration --input export-config.json --output migration-package.json
覆盖包内导入策略 导入时追加 --id-strategy--conflict-strategy--create-app-api-token-on-import
携带密钥迁移(受控通道) 配置/向导中 include_secrets: true,按机密文件传输并在导入后按需删除包
登录后查看全文
热门项目推荐
相关项目推荐