你停掉 Gitea,安装 Forgejo,复制 app.ini 和数据目录,然后按照所有迁移指南描述的方式启动服务。网页界面出现了。接着数据库迁移失败,或者更糟:Forgejo 直接拒绝启动,因为你的 schema 版本太新,它无法识别。你使用的是 Gitea 1.23 或更新版本,而所有文档描述的那种简单原地升级,已经不再适用于你了。
Forgejo 在 2024 年初成为 Gitea 的硬分叉,此后两个项目在数据库 schema 层面已经分道扬镳。现在如何把 Gitea 迁移到 Forgejo,完全取决于你的 Gitea 版本。本指南会给出适合你版本的确切路径、原生安装和 Docker 安装的完整命令、针对 Gitea 1.23+ 的变通方案,以及一份验证清单,让你在迁移过程中不丢失仓库、issue 或用户。
TL;DR(太长不看版)
- 这道版本鸿沟是真实存在的。 Forgejo v10.0 是最后一个支持从 Gitea 无缝直接升级的版本。Gitea v1.22 是能够走受支持的直接路径的最高版本。
- 在 Gitea v1.22 或更早版本上: 先迁移到 Forgejo v10.0.x,再把 Forgejo v10 升级到当前版本(v15)。两跳都受支持。
- 在 Gitea v1.23 或更新版本上: 官方并不支持直接迁移路径。你需要在 SQL/schema 降级、社区迁移脚本、仓库/API 迁移,或者使用上述方法之一进行的全新服务器分阶段迁移之间做选择。每种方式都伴随着不同程度的数据丢失代价。
- 在动手之前,先备份你的数据目录和数据库。 下面的每一条路径都假定你已有可恢复的备份。不受支持的路径则要求你把这一点做两遍。
- 有些事项在迁移完成后始终需要关注: 计划重新创建或审计个人访问令牌,重新注册 Actions runner,并删除后重建 Bleve 搜索索引。无论选择哪条路径,都要为这些事项做好准备。
- 迁移并非强制性的。 Gitea 处于积极维护状态。如果就你的情况而言,迁移风险超过了收益,“留在 Gitea 上”这个决定就是站得住脚的。
本文不涉及:从 Gogs、GitHub 或 GitLab 迁移到 Forgejo,以及从零开始搭建 Forgejo Actions。
前提条件/你需要准备的东西
在运行任何一条命令之前,先确认你已经拥有两样东西的完整、可恢复的备份:你的 Gitea 数据目录(默认原生安装下是 /var/lib/gitea)和你的数据库。如果你的数据库是 SQLite,该文件就在数据目录内;如果是 PostgreSQL 或 MySQL,请用 pg_dump 或 mysqldump 单独导出。没有经过验证的备份,迁移就不是迁移,而是赌博。
你还需要准备:
- 你当前的 Gitea 版本。运行
gitea --version(或docker exec <container> gitea --version)。这个版本号决定了你的整条迁移路径。 - 你的数据库类型:SQLite、PostgreSQL 或 MySQL。部分变通方法是针对特定数据库的。
- 服务器上的 root 或 sudo 权限。
- 要清楚,受支持的路径是把 Forgejo v10.0 作为中间跳板来安装的。Forgejo v10.0 自 2025 年 4 月 16 日起已经 EOL。你只在迁移期间安装它,一完成就立即升级离开它。
为什么直接升级路径失效了
2024 年 12 月,Forgejo 项目发布了一篇 兼容性声明 划出了一条明确的界线:“未来的 Forgejo 版本将不再支持从运行 v1.23 或更高版本的 Gitea 实例进行升级。”这一句话,就是整个问题的核心所在。
Forgejo 在 2024 年初从 Gitea 分叉出来后,两个代码库不断分化,数据库 schema 也随之逐渐产生差异。Forgejo 维持了一个兼容窗口期,让现有的 Gitea 运维人员可以顺利迁移过去,但这个窗口期终究会结束。2025 年 1 月 16 日发布的 Forgejo v10.0,是 "the last version to allow a transparent upgrade from Gitea v1.22 or lower." 任何比 v10.0 更新的版本都无法直接读取 Gitea 数据库。
让人措手不及的是时间点。Gitea v1.23 于 2024 年末发布,而 截至 2026 年 6 月,Gitea 的当前版本是 v1.26.4。如果你的 Gitea 实例保持得比较新,那你已经越过了断层,标准教程已经不再适用于你的情况。
本节要点:受支持的直接路径最高只支持到 Gitea v1.22。更新的版本都需要额外一步。
根据你的 Gitea 版本找到对应的迁移路径
运行 gitea --version,找到对应的行,然后按照第三列中的路径操作。本指南其余的内容都是这些行背后的细节。
| Gitea 版本 | 受支持的 Forgejo 目标版本(直接迁移) | 迁移路径 |
|---|---|---|
| ≤ 1.21 | Forgejo v7.0 – v10.0 | 切换到 Forgejo v10.0.x,再升级到当前版本(v15) |
| 1.22 | Forgejo v8.0 – v10.0 | 切换到 Forgejo v10.0.x,再升级到当前版本(v15) |
| 1.23 | 无(不支持) | SQL 版本补丁(方案 A),然后走受支持的路径 |
| 1.24 – 1.25.2 | 无(不支持) | 连续 SQL 降级脚本(方案 B),然后走受支持的路径 |
| 1.26+ | 无(不支持) | 实验性脚本或逐仓库的 API 迁移(方案 C) |
版本兼容性说明直接来自 Forgejo 官方兼容性公告:Gitea v1.21 及以下版本可以迁移到 Forgejo v7.0 至 v10.0,而 Gitea v1.22 可以迁移到 Forgejo v8.0 至 v10.0。两者最高都只能迁移到 v10.0,这是最后一个受支持的直接目标版本。
在版本问题之下,其实有三种迁移策略,在看具体步骤之前先了解一下会很有帮助:
- 原地升级,同一台服务器。 在现有服务器上替换二进制文件或容器镜像。当你的版本支持时,这是成本最低的方式,需要经过 v10.0 这个中间跳板版本。
- 全新服务器暂存。 准备一台新服务器,将其作为与你的 Gitea 版本相匹配的迁移方法的安全目标。这样可以保护原有的 Gitea 服务器,也让回滚更容易,但并不能绕开 Gitea 1.23+ 的数据库 schema 问题。
- 仓库/API 迁移。 使用 Forgejo 的迁移工具从旧的 Gitea 实例中拉取仓库。Git 历史记录是可靠的部分;issue、PR、标签、发布版本、wiki 数据和评论等元数据应视为尽力而为,需要逐仓库验证。

受支持的路径:Gitea v1.22 或更早版本
如果你使用的是 Gitea v1.22 或更早版本,那么 官方迁移文档 描述了一条两步路径:先迁移到 Forgejo v10.0.x,再把 Forgejo v10 升级到当前版本。第一步是直接替换,因为 Forgejo v10.x 能够读取 Gitea v1.22 的数据库;第二步则是常规的 Forgejo 升级。
具体做法有两种,取决于 Gitea 在你服务器上的运行方式。选择与你现有配置相匹配的那种:如果 Gitea 是以系统服务方式运行,使用原生路径;如果 Gitea 运行在容器中,使用 Docker 路径。Forgejo v10.x 可以直接替代 Gitea v1.22.x。
原生(systemd)路径
先备份。然后使用你所在发行版的软件包或固定在 v10.0 的发行版二进制文件安装 Forgejo v10.0.x。在复制任何内容之前先停止 Gitea。正在运行的服务如果在复制过程中写入其数据目录,会导致复制内容损坏。
# Stop the running Gitea service before touching its data
systemctl stop gitea
将配置文件和数据目录复制到 Forgejo 对应的位置,然后修正所有权。配置文件必须能被 forgejo 组读取;数据必须归 forgejo 用户所有。
# Copy the existing Gitea config to Forgejo's config path
cp /etc/gitea/app.ini /etc/forgejo/app.ini
# Back up the copied config before rewriting paths
cp /etc/forgejo/app.ini /etc/forgejo/app.ini.bak
# Update old Gitea data paths to Forgejo data paths
sed -i 's#/var/lib/gitea#/var/lib/forgejo#g' /etc/forgejo/app.ini
# Forgejo reads its config as the forgejo group; grant group write
chown root:forgejo /etc/forgejo/app.ini && chmod g+w /etc/forgejo/app.ini
# Copy the data directory and hand ownership to the forgejo user
rsync -aHAX --numeric-ids /var/lib/gitea/ /var/lib/forgejo/
chown -R forgejo:forgejo /var/lib/forgejo
启动 Forgejo,并设置为开机自启。
# Start Forgejo and enable it at boot
systemctl start forgejo && systemctl enable forgejo
在从 v10.0 升级之前,先清空队列,以免在版本跳转过程中丢失正在进行中的任务。
# Flush pending queue items before the version upgrade
forgejo manager flush-queues
现在按照标准流程,用 v15 替换二进制文件并重启服务,把 Forgejo v10.0.x 升级到当前版本,参见 Forgejo 升级指南。完成这两步跳转后,运行 doctor 来检查数据库并修复能修复的问题。
# Check everything; write a log you can read if something is wrong
forgejo doctor check --all --log-file /tmp/doctor.log
阅读 /tmp/doctor.log 再应用任何自动修复。使用 --fix 仅在 doctor 输出指出了你理解的特定修复方法,或者你正在遵循官方的故障排查流程时才这样做。
如果 doctor 没有报告严重错误,并且 web UI 列出了你的仓库,那么第一阶段的迁移就完成了。
Docker / 容器路径
如果 Gitea 是以容器方式运行的,Forgejo 也应该以同样的方式运行。用 Forgejo v10.x 镜像直接替换你的 Gitea v1.22.x 镜像,让它指向同一个数据卷和数据库。先确认你的 Docker 版本至少是 20.10.6。更旧的版本在使用 Forgejo 容器时会产生未定义行为。
有一个容器特有的细节容易让人踩坑:环境变量。为了兼容性,Forgejo 保留了 GITEA_ 前缀,但文档建议同时传入两种前缀,这样即使未来某个 Forgejo 版本去掉了旧的命名,你的配置也不会失效。
# docker-compose.yml (excerpt): pass both prefixes for forward compatibility
environment:
- GITEA__database__DB_TYPE=postgres
- FORGEJO__database__DB_TYPE=postgres
# ...repeat the dual-prefix pattern for every config override you pass
对于 rootless 容器,数据必须归用户 1000 和组 1000 所有,与 rootless 镜像的运行时用户保持一致。容器在 v10.x 上启动后,运行同样的 forgejo doctor check --all 在容器内部执行,然后把镜像标签升级到当前的 v15 版本并重启。
如果你使用的是 Gitea v1.23 或更新版本

标准指南写到这里就止步了,而这也正是目前大多数实际安装所处的位置。官方并不支持从 Gitea v1.23+ 直接迁移的路径,因此下面的每个方案要么来自社区,要么会有数据损失。在运行任何一个方案之前,请对数据目录和数据库做完整备份,并确认能够成功恢复。这些方法都会修改生产数据;备份是你唯一的撤销手段。
这三种方案在风险和数据保留程度上各不相同。选一个你能接受其取舍的方案。
方案 A:SQL Schema 降级/版本补丁
最简化的变通方法,是把数据库 schema 版本降级,让 Forgejo v10 把它当作 v1.22 的数据库来处理。
注意:不要把版本号更新本身当作完整的降级操作。记录的迁移版本号必须与实际的 schema 相匹配。在 Gitea 1.23.x 上,先在副本上测试完整的降级流程,比较 schema 之后,再让 Forgejo 指向它。
一位 Forgejo 维护者在其中描述了这段 SQL Codeberg issue #7638:
-- Downgrade the recorded schema version to the v1.22 baseline (PostgreSQL/MySQL)
UPDATE version SET version=305 WHERE id=1;
完成这一步之后,你就可以按照支持的路径操作,就像你使用的是 v1.22 一样。关键的警告是:该维护者只确认了这一点适用于 Gitea v1.23.1 这一具体版本,而不是整个 v1.23.x 系列。schema 版本号必须精确;把错误的目标值应用到错误的 Gitea 版本上,可能会让数据库进入任何迁移都无法恢复的状态。这仅适用于 PostgreSQL/MySQL,并非官方支持的做法,也没有作为通用流程进行测试。
小贴士:直接将 version 设置为 305 会跳过 304→305 这次迁移,而这次迁移正是改变了 TOTP 密钥的编码方式。 Codeberg issue #8210 文档记录了这会导致受影响用户的双重身份验证损坏。修复方法是将 version 设置为 304,让迁移正常执行,或者清空 two_factor 表中的记录,让用户重新注册。如果有任何账号使用了 TOTP,请不要跳过这一步。
方案 B:连续 SQL 降级脚本(Gitea 1.24–1.25.2)
对于 1.24 到 1.25.2 之间的版本,光靠单次版本号修补是不够的:schema 经历了好几个中间状态。位于 xlrl/prepare-gitea-migration-to-forgejo 让 schema 逐级回退到 v1.22,之后你就可以走受支持的路径了。这些脚本是社区的成果,并非权威流程,且主要针对 SQLite 场景。请只对已经备份过的数据库运行它们。
方案 C:逐仓库 API 迁移(Gitea 1.26+ 或任意版本)
仓库/API 路径对 Git 数据的保留最为可靠。根据源端、权限和迁移方法的不同,issue、pull request、标签、里程碑、发布版本、wiki 数据和评论等可选元数据也可能一并迁移过来,但这些应视为尽力而为,并需要逐仓库验证。如果只是代码归档,这通常没问题;但如果服务器上的 issue 跟踪系统承载着团队的记忆,在采用这条路径之前,务必先测试元数据的迁移效果。
有两款实验性工具尝试为 1.23+ 版本提供更完整的迁移,但它们各自也存在缺陷。 pacnpal/gitea2forgejo (GitHub)会执行完整转储加 API 同步,需要匹配的 SECRET_KEY 才能迁移加密字段,但它不会迁移 Personal Access Tokens、Actions runners、webhook 回调 URL 或 2FA。 nicoverbruggen/gitea-to-forgejo (GitHub)是一个基于 Podman 的实验性工具,可迁移用户、密钥、组织、仓库、issues、PR、发行版和镜像,但不迁移 2FA 令牌、Actions 运行时数据或悬空的 OCI 包清单。请将两者都视为实验性方案,并对照下面的迁移矩阵核实结果。
本节要点:官方并未支持从 Gitea 1.23+ 直接迁移的路径。选择一个你能接受其数据丢失代价的方案,并在开始前先做好备份。
在全新服务器上迁移更安全,但并不能绕过版本断层
一台全新的服务器是暂存和验证迁移最安全的地方,但它并不是另一条独立的 schema 兼容路径。如果你的源版本是 Gitea v1.22 或更早,先在目标服务器上安装 Forgejo v10.0.x,将 Gitea 数据恢复或复制过去,让 Forgejo 执行受支持的数据库迁移,然后再把这个 Forgejo v10 实例升级到当前的 Forgejo 版本。
如果你的源版本是 Gitea v1.23 或更新版本,不要指望把 Gitea 数据库直接恢复进当前的 Forgejo 就能启动。你仍然需要用到上面提到的某一种不受支持的 1.23+ 方案:分阶段的 SQL 降级或回退、社区迁移工具,或者逐仓库的 API 迁移。全新服务器能降低回滚风险,因为原来的 Gitea 机器保持不变;但它并不能消除处理数据库版本不匹配的必要性。
这就是 Cloudzy 的一键 Forgejo 应用可以帮上忙:把它作为干净的目标实例,然后运行与你的 Gitea 版本相匹配的迁移方法。Cloudzy 的应用市场镜像可以在我们的 高性能 VPS,这样你就能专注于恢复、验证和切换步骤,而不是初始安装。迁移方案仍然取决于你处于 Gitea 1.22 断层之前还是之后。
哪些会自动迁移,哪些必须手动重做

你的部分数据会自动迁移,部分数据只有在满足特定条件时才会迁移,还有一部分数据永远不会自动迁移,必须手动重建。在开始之前弄清楚哪些属于哪一类,决定了你得到的是一次干净利落的切换,还是一整周用户困惑不已的局面。
| 项目 | 是否迁移? | 条件 |
|---|---|---|
| 用户账号 + 密码 | 是 | 自动 |
| 用户的 SSH 公钥 | 是 | 自动 |
| SSH 主机密钥(服务器身份标识) | 否(需手动) | 复制 /etc/ssh/ssh_host_*,否则每个客户端都会遇到主机密钥不匹配的问题 |
| 仓库 + git 历史记录 | 是 | 通过复制数据目录 |
| Issue、PR、标签、里程碑、评论 | 是 | 通过数据库 |
| LFS 对象 | 是 | 核对对象数量和总大小是否一致 |
| Webhooks(配置) | 是 | 投递历史不会被迁移 |
| CI/CD 密钥 | 有条件 | 只有当 SECRET_KEY 匹配时才可以,否则会静默变得不可读 |
| OAuth 应用 | 有条件 | 仅当 SECRET_KEY 匹配时;回调 URL 可能需要更新 |
| 2FA(TOTP 种子) | 有条件 | 仅当 SECRET_KEY 匹配时 |
| 个人访问令牌 | 计划重新创建/审计 | 迁移后最安全的做法,在依赖旧令牌之前先进行验证 |
| Actions runner 注册信息 | 否(必须重新注册) | 注册令牌的作用域限定在主机名上 |
| Actions 运行历史 / 日志 | No | 任何路径都不会迁移 |
| Bleve 搜索索引 | 否(删除后重建) | 删除 /var/lib/gitea/data/indexers/,让它重新生成 |
| 自定义品牌 / 模板 | 是(文件形式) | public/assets/img/ 现在必须放在 custom/ 目录下 |
| 按仓库配置的部署密钥 | 否(需手动重新添加) | 任何路径都不会迁移 |
有四项内容受影响最大,因为它们会悄无声息地失效,或者影响到每一个用户,因此要刻意处理好:用户应该重新创建或审查自己的 Personal Access Tokens;你必须针对新主机名重新注册 Actions runners;你必须删除 Bleve 搜索索引,让 Forgejo 干净地重建它;并且你必须重写任何 uses: 把 Actions 工作流中的简短引用改成完整的 GitHub URL,因为 Forgejo 和 Gitea 是从各自独立的镜像拉取 action 的,相对引用会指向错误的位置。
小贴士:如果你在迁移期间并行运行 Gitea 和 Forgejo,且两者指向同一个 Redis 实例,请在开始前把 Forgejo 改为使用 Redis db=1。两者默认都是 db=0,如果共用同一个库,Gitea 会消费掉 Forgejo 的迁移事件,从而破坏迁移队列。(blog.mei-home.net documents this exact failure.)
本节要点:个人访问令牌、Actions runner、按仓库配置的部署密钥,以及 Bleve 搜索索引,始终需要手动处理。把它们安排为迁移任务,而不是事后才想起来的补丁。
验证迁移是否成功
先运行 doctor 检查,再核对各项数字,最后测试用户实际会做的操作。doctor 检查能捕捉数据库层面的问题;而手动检查能发现 doctor 看不到的数据丢失和访问权限问题。
- 运行
forgejo doctor check --all --log-file /tmp/doctor.log,然后在做任何改动之前先读日志。使用--fix仅用于你理解其含义的特定修复操作。 - 确认仓库数量、issue 和 PR 数量,以及 LFS 对象数量和总大小都与迁移前的数字一致。如果这里出现不匹配,说明有些内容没有迁移过来。
- 测试一次 SSH 的 push 和 pull。如果客户端报告主机密钥不匹配,说明你没有把 /etc/ssh/ssh_host_* 复制到新服务器上。
- 登录并确认使用 2FA 的账户功能正常。如果 TOTP 出现问题,请参见上面方案 A 的注意事项。
- 重新生成个人访问令牌,重新注册 Actions runner,并运行一次测试工作流。
迁移后出现的几种症状都有已知原因。favicon 或 logo 缺失,说明你的品牌资源仍然在 public/assets/img/ 而不是 Forgejo 现在期望的 custom/ 目录中。包路由出现 404 错误,说明存储路径配置有误。Actions 报“repository not found”错误,是 uses: 镜像问题。改用完整 URL。使用 SQLite 数据库时登录后出现空白页或 500 错误,说明你的配置低于最低要求,而 升级文档 定在了 Forgejo v1.19.3-0。
什么情况下继续留在 Gitea 更合理
迁移是一种选择,而不是一种义务。如果你已经越过了那个断层,而就你的情况而言,迁移的复杂度超过了治理结构或功能上的收益,那么留在 Gitea 上就是一个正当的选择。Gitea 处于积极维护状态(每月发布多个版本,目前为 v1.26.4),而且 CommitGo 提供包括 Gitea Enterprise、Gitea Cloud 以及 SOC 2 Type 2 认证 提供给需要这些功能的团队。
还有一个需要如实权衡的问题:中间版本已经 EOL。受支持的直接路径要求短暂安装 Forgejo v10.0,而该版本自 2025 年 4 月起已经 EOL。作为一个会立刻升级离开的临时迁移步骤,这是可以接受的,但出于政策原因,有些运维人员会对此有异议。对他们来说,迁移到全新 Forgejo 实例的仓库/API 路径可以完全避开这个 EOL 跳板,而全新服务器分阶段迁移,仍然取决于你选择的迁移方法。
如果你迁移的理由是开发活跃度,数据支持 Forgejo 是更活跃的项目:一份来自 honeypot.net 统计显示,在 2024 年 7 月到 2025 年 5 月期间,Forgejo 有 3,039 次提交,而 Gitea 有 1,228 次;在截至 2025 年 5 月的一年里,Forgejo 有 232 位贡献者,Gitea 有 153 位。这只是众多信号中的一个,应该拿来和你的迁移风险一起权衡,而不是当作决定性的依据。
本节要点:迁移并非强制性的。这是在治理与活跃度偏好,同迁移风险之间做权衡的决定,两种选择都说得通。
选定路径,并先做好备份
你的迁移路径归结为一个数字:你的 Gitea 版本。在 v1.22 或更早版本上,你可以走经过 Forgejo v10 的受支持两步路径。在 v1.23 或更新版本上,你需要根据自己能接受的数据丢失程度选择变通方法:SQL/schema 降级、社区脚本、仓库/API 迁移,或全新服务器分阶段迁移。无论走哪条路径,都要先备份数据目录和数据库,然后运行 forgejo doctor 之后运行一次,并确认仓库、issue 和 LFS 的数量与迁移前一致。
确认你的版本,做好备份,然后按照对应的那一行操作。这条路径比旧教程承诺的要复杂一些,但只要你清楚自己在版本断层中的位置,路径本身是明确的。
常见问题
我能直接从 Gitea 升级到 Forgejo 吗?
如果你运行的是 Gitea v1.22 或更早版本,答案是可以:直接升级到 Forgejo v10.0,再把 Forgejo v10 升级到当前版本。如果你运行的是 Gitea v1.23 或更新版本,答案是不可以。Forgejo v10.0 是最后一个支持透明直接升级的版本,因此更新的 Gitea 版本需要使用变通方法。
哪个版本的 Gitea 与 Forgejo 兼容?
Gitea v1.22 及更早版本有受支持的直接升级路径可以升级到 Forgejo(最高到 Forgejo v10.0,之后再升级到当前版本)。Gitea v1.23 及更新版本没有官方支持的直接路径,因为 Forgejo 从 v10.0 之后的版本起,就不再支持从 Gitea v1.23+ 无缝升级了。
我能从 Gitea 1.26 迁移到 Forgejo 吗?
可以,但不能通过受支持的直接升级方式。你的选择包括:仓库/API 迁移(Git 历史记录是可靠部分,元数据需要逐仓库测试)、一个实验性迁移脚本,或者使用上文提到的某种不受支持的 1.23+ 方法进行全新服务器分阶段迁移。从 Gitea 1.26 并没有官方支持的原地迁移路径。
我的 Personal Access Tokens 会被迁移过去吗?
计划在迁移后重新创建或轮换个人访问令牌。部分迁移路径不会保留它们,即使令牌得以保留,用户在再次依赖之前也应审查其权限范围。任何使用旧令牌的脚本或集成,都应在切换后进行测试并更新。
我的 Gitea Actions workflow 能在 Forgejo 中运行吗?
大部分可以,但有两处必须修复。Workflow uses: 简短引用会失效,因为 Forgejo 和 Gitea 是从各自独立的镜像拉取 action 的。请把它们改成完整的 GitHub URL(例如 uses: https://github.com/sammcj/dotenv-output-action@main)。你还必须重新注册你的 Actions runners,因为它们的注册令牌是绑定到旧主机名的。
那从 Gogs 迁移到 Forgejo 呢?
那是另一种迁移,路径也不同,不在本文讨论范围内。本指南专门针对从 Gitea 迁移到 Forgejo;如果源端是 Gogs,请查阅 Forgejo 官方迁移文档中针对 Gogs 的具体流程。