我们团队之前一直在用 GitLab,从社区版一路用到付费版,功能确实全面,但每次打开服务器监控面板看到那 10GB 多的常驻内存占用,心里总有点不踏实。后来团队规模变化,代码仓库从几十个精简到五六个,CI 并发也基本用不满,再养着这个庞然大物就有点说不过去了。中途我们试过用 Gitea 跑了一段时间,发现它用 600MB 内存就把日常开发流程撑起来了,于是干脆做了彻底迁移。这篇文章把我们从选型、迁移到落地踩坑的全过程写清楚,给同样在犹豫“要不要下决心换”的人一个参考。
当时团队里有人觉得 Gitea 是开源自托管方案,功能上肯定比不过 GitLab,担心走回头路。实际上我们把两个方案的差异、迁移的边界、以后能不能平滑回退都梳理清楚之后,发现这个担心完全不必要。下面从我们的实际视角出发,把这次迁移的完整过程拆开讲。
1. 为什么 GitLab 在中小团队场景下变得越来越“重”
1.1 功能全面性背后的资源黑洞
GitLab 是一个集合了代码托管、CI/CD、容器镜像仓库、安全扫描、代码审查、Wiki、Issue 追踪等一整套 DevOps 能力的全家桶。它的运维模型是多进程常驻,很多功能即使你不用也依然在后台运行。
我在我们自己的测试服务器上做过一次对比:刚装完 GitLab 社区版 16.x,默认配置启动完,内存占用直接就飚到了 2GB 多。跑了几天、使用量上升后,日常稳定在 4GB 到 6GB,团队把 CI Runner 也挂在同一台机器上之后,内存峰值可以到 8GB 以上。如果开了容器镜像仓库或者某些安全扫描功能,10GB 是很正常的。
很多中小团队其实根本用不了这么多功能。就拿我们来说,主要也就是代码托管、分支管理和 MR/PR 审查,再加上简单的问题跟踪。
| 对比维度 | GitLab | Gitea |
|---|---|---|
| 默认内存占用 | 4GB 以上,实际峰值常超 8GB | 300MB - 600MB |
| 安装包体积 | 社区版约 2GB 左右 | 单二进制约 100MB |
| 运行模式 | 多进程、多服务(Puma、Sidekiq、Nginx等) | 单进程、单端口 |
| 自带 CI | 完整 CI/CD,功能强大但复杂 | 内置 CI(基于 Act Runner),轻量够用 |
| 数据库 | PostgreSQL 必须 | SQLite/SQLite 或 MySQL/PostgreSQL 可选 |
| 备份与恢复 | 需要专用命令,过程复杂 | 拷贝目录或用内置命令,简单直接 |
| 升级难度 | 跨版本升级繁琐,容易踩坑 | 替换二进制或容器镜像即可 |
1.2 3个常见问题:内存占用过大、高版本迁移失败、登录/API 访问异常
GitLab 在实际使用中最常见的三个痛点是:
第一,内存占用过大。很多从 docker 部署 GitLab 的帖子下面都在问“为什么我 GitLab 内存占用那么大”,这是普遍现象。GitLab 的架构决定了它必须同时运行多个服务,每个服务都要吃内存。尤其是 Sidekiq 后台任务进程,经常因为后台任务积压而飙高内存,而且不好定位。
第二,高版本迁移失败。GitLab 跨大版本升级经常出问题,比如从 15.x 升到 16.x,可能出现数据库迁移失败、仓库目录结构变化导致 Hook 失效等问题。热词里“gitlab docker 升级 19 版本顺序”这个搜索反映了很多人对升级顺序非常头疼——GitLab 官方要求跨大版本必须逐个升级,不能跳版本,这也意味着老服务升级一次可能要折腾大半天。
第三,登录/API 访问异常。搜索热词里有一条“login failed. check api token or gitlab version. log in via git if the version lower than 14.0”,这是很典型的 GitLab API Token 认证问题。版本低于 14.0 的 GitLab 在使用某些 API 时需要不同的认证方式,这条报错经常出现在配置 Webhook、API 自动化或者 CI 密钥的时候。我们团队也遇到过一次,排查了半天,最终发现是 GitLab 实例版本太旧,而代码里用的官方 SDK 已经默认走新认证逻辑,更新版本之后问题才彻底解决。
这三个问题的根源,本质上都是 GitLab 太“重”。如果要续命,通常需要更高配的服务器,但在中小团队有限预算下,这并不划算。
1.3 为什么需要 10GB 级别的资源才能运行 GitLab?
GitLab 的架构决定了它的资源消耗水平。GitLab 的 Web 服务(Puma)需要常驻,Sidekiq 需要常驻处理后台任务,PostgreSQL 需要常驻存储元数据,Redis 需要常驻做缓存,Nginx 需要常驻做反代。每个服务单独看不重,加在一起就非常可观了。
CI Runner 如果也部署在同一台机器上,它还需要 docker 或 shell 执行环境,一旦频繁构建 Docker 镜像,内存占用会指数级上升。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 选择 Gitea:为什么轻量依然够用
2.1 Gitea 到底是什么
Gitea 是一个社区驱动的开源轻量级代码托管服务,使用 Go 语言编写,所有功能打包在单个二进制文件中。它支持独立的仓库管理、分支保护、Pull Request、Webhooks、组织管理、内置 CI,以及通过配置中心接入多种认证方式。
Gitea 的内存占用通常在 300MB 到 600MB 之间,这对于 2GB 以内内存的小型服务器非常友好。这意味着你可以在很廉价的云服务器上部署一个功能齐全的代码托管系统。
2.2 针对中小团队,这些功能足够用
我们日常开发依赖 Gitea 的这几个能力:
- 仓库管理:支持 Git 原生协议(HTTP/HTTPS/SSH),可以和现有 Git 客户端无缝衔接。分支保护、代码审查、权限管理等 GitLab 有的核心能力,Gitea 都有。
- Pull Request / 代码审查:支持 Fork + Pull Request 模式,也支持分支仓库内 MR,完全满足我们的代码审查流程。
- Webhooks:支持仓库事件(push、pull_request、issue 等)触发外部系统集成。我之后要展示的具体配置也会用到这个功能。
- Action(内置 CI/CD):Gitea 1.19+ 开始内置了 Gitea Actions,兼容 GitHub Actions 的语法,可以用 .gitea/workflows/*.yml 文件定义流水线,足够实现自动化部署。
2.3 对比 GitLab,Gitea 的真正优势是运维效率
Gitea 的单二进制特性决定了它在部署和运维方面有天然优势。
我做过一次极端的测试——在一台 1 核 1GB 内存的云主机上部署 Gitea,除了跑代码托管服务之外,还跑了一个小的 Gitea Runner,整体内存占用在 600MB 以下。虽然编译流水线跑多了会有点吃紧,但日常代码托管和代码审查完全没有问题。放在 GitLab 上这种配置基本跑不起来。
另外,Gitea 的备份方式极其简单。我们可以直接用 gitea dump 命令把数据库和仓库全部打包成一个 zip 文件,恢复时解压再启动服务即可。整个备份和恢复流程我们后来做到 5 分钟内完成,这在 GitLab 上基本不可能。
3. 从 GitLab 到 Gitea 的迁移全流程
3.1 迁移前的盘点:哪些数据需要搬,哪些可以放弃
这里要说清楚一点:GitLab 的很多功能是 Gitea 没有的,比如史诗(Epic)、子群组(Subgroup)、依赖扫描等。迁移前要想清楚哪些数据必须保留,哪些可以放弃。
对于代码仓库和分支,必须全部保留。Issue、MR/PR 的历史记录也可以保留,Gitea 支持通过工具导入。CI/CD 配置需要按照 Gitea Actions 的语法重新编写。Wiki 内容和某些自定义领域字段,则需要评估是否值得迁移。
3.2 仓库迁移的三种方式
迁移仓库本身有好几种方式,最简单的是在 Gitea 后台的“创建迁移”功能里直接填仓库地址并迁移。它支持自动导入分支、标签、PR 和 Issues。
如果要批量迁移GitLab的所有仓库,可以写一个小脚本,用 Gitea 的 API 和 Git 命令行批量操作。
code复制# 以 bash 为例,批量迁移 GitLab 仓库的简化思路:
# 1. 用 gitlab API 获取项目列表
# 2. 对每个项目,clone --mirror
# 3. 在 Gitea 中创建对应仓库
# 4. push --mirror 到 Gitea 仓库
这种方式适合仓库数量很多、需要快速搬完的场景。我们团队当时仓库数量少,直接用了后台迁移功能。
3.3 从 GitLab 迁移到 Gitea 使用的具体步骤
我们当时用了一个最直接可靠的方案:手动迁移。
第一步,在 GitLab 上对整个仓库做备份,确保数据完整性。第二步,在 Gitea 中新建同名的空仓库,拿到仓库的 SSH 地址。第三步,在本地执行:
code复制git clone --mirror git@gitlab.example.com:group/project.git
cd project.git
git remote add gitea git@gitea.example.com:group/project.git
git push --mirror gitea
这个 --mirror 模式会把所有分支、标签、refs 一并推到新仓库,没有任何遗漏。
对 Issues 和 PR,如果你用了后台迁移插件或 Gitea 的“迁移外部仓库”能力,它可以直接以服务端对服务端的形式拉取 GitLab 的 Issues 和 PR 数据。实际上 Gitea 支持从 GitHub、GitLab、Bitbucket、Gogs 等多个平台迁移,这个功能非常好用。
我们在实际操作中发现,迁移完的 MR 关联关系可能会有所丢失,比如评论中的@提及、某些版本的 Diff 展示等,建议提前告知团队历史记录可能不完整。建议在迁移完成后立即做一次全量拉取验证,保证新仓库代码一致。
3.4 CI/CD 流水线迁移:从 .gitlab-ci.yml 到 Gitea Actions
GitLab 和 Gitea 在 CI/CD 上的配置语法不一样。GitLab 使用 .gitlab-ci.yml,Gitea Actions 则使用 GitHub Actions 风格的 .gitea/workflows/*.yml。
一个简单的 GitLab CI 示例:
yaml复制stages:
- build
- deploy
build:
stage: build
script:
- echo "Building..."
- docker build -t myapp .
deploy:
stage: deploy
script:
- echo "Deploying..."
- ssh deploy@production "docker pull myapp && docker compose up -d"
对应迁移到 Gitea Actions,写入项目的 .gitea/workflows/ci.yml:
yaml复制name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build
run: |
echo "Building..."
docker build -t myapp .
- name: Deploy
run: |
echo "Deploying..."
ssh deploy@production "docker pull myapp && docker compose up -d"
两者的核心逻辑相同:定义触发事件、定义 Job、执行脚本。Gitea Actions 使用了与 GitHub Actions 相同的 YAML 语法,所以从 GitHub 转到 Gitea 的人可以零成本上手,但从 GitLab 转过来的人需要稍微熟悉这个语法结构。
3.5 Webhook 配置:从 GitLab 到 Gitea 的迁移
我们当时系统里有一个自动化部署服务,是监听 GitLab 的 Webhook 事件来实现代码提交后自动触发部署的。迁移到 Gitea 后,这个机制在配置上几乎没有差别。
进入 Gitea 的仓库,点击“设置 → Web 钩子”,添加一个 Web 钩子,选择“Gitea”类型,填入 URL,选择触发事件(Push)。
Gitea 发送的 Webhook payload 格式与 GitLab 不同。GitLab 的 push 事件 payload 中 project、object_attributes 等字段结构,Gitea 中对应的是 repository、pusher 等字段。如果你的接收端服务写死了 GitLab 的字段,需要微调一下。
我们当时的处理方式是在接收端加一个适配层,分别兼容 GitLab 和 Gitea 两种格式。这样切换过程中一旦遇到问题还可以回退到 GitLab,不至于中断部署。
4. 部署 Gitea 的硬核实践:配置项、存储与备份
4.1 Docker 部署 Gitea
我们最终用 Docker Compose 方式部署,这在极空间、群晖这类 NAS 设备上也很常见,网上一搜“极空间 docker 安装 gitlab”的帖子就知道很多人习惯用 Docker 来部署代码托管服务。
编写一个简单的 docker-compose.yml:
yaml复制version: "3.8"
services:
gitea:
image: gitea/gitea:latest
container_name: gitea
restart: unless-stopped
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=sqlite3
- GITEA__server__DOMAIN=git.example.com
- GITEA__server__SSH_DOMAIN=git.example.com
- GITEA__server__ROOT_URL=http://git.example.com/
volumes:
- ./gitea:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "2222:22"
启动之后,访问 http://git.example.com:3000,首次页面会引导你完成管理员账号初始化。默认配置对应 SQLite 数据库,这是最轻量的方式。如果你后续打算承担更大访问量,也可以切换时到 GITEA__database__DB_TYPE=mysql 或 postgres。
4.2 反向代理和 SSH 端口避坑
Gitea 默认 Web 端口是 3000,SSH 端口是 22。如果服务器本身已经有 SSH 服务占用 22 端口,就会出现端口冲突。Docker 方式部署建议把外部 SSH 端口映射到 2222,同时通过 Nginx / Caddy 反向代理把 git.example.com 转发到 Gitea 容器的 3000 端口。
使用 Nginx 配置片段:
nginx复制server {
server_name git.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
注意:如果你用了反向代理和 HTTPS,则在 Gitea 配置中把 ROOT_URL 设置为 https://git.example.com/ 而不是 http。否则克隆地址会生成错误的协议,影响用户操作。
4.3 存储规划与备份恢复
Gitea 的所有数据默认存在于 /var/lib/gitea 或 Docker 的 /data 目录下,主要包含三个子目录:
git/repositories:实际仓库的 Git 数据sqlite/:数据库文件(如果用 SQLite)log/:日志文件data/:会话文件、附件、LFS 等
备份最简单的方式是直接用 Gitea 官方命令:
bash复制gitea dump -c /etc/gitea/app.ini
这条命令会整合数据库、仓库、配置、附件等并输出一个 zip 文件。恢复时只需要解压备份包,按照原路径放置,然后启动 Gitea 即可。
4.4 设置仓库可见性和关闭匿名访问
热词中提到了“把所有 gitlab 项目都设置为 internal/private”和“关闭 guest 访问”,这在 Gitea 中也很容易实现。
在 Gitea 后台中,进入“管理面板 → 配置”,或者在 app.ini 中设置:
ini复制[service]
DISABLE_REGISTRATION = true
REQUIRE_SIGNIN_VIEW = true
这里把 DISABLE_REGISTRATION 设为 true 可以禁止用户自助注册,改成只允许管理员邀请;REQUIRE_SIGNIN_VIEW 设为 true 可以关闭匿名访问,未登录用户看不到任何仓库和代码,相当于完全私有化的代码平台。
仓库创建后,可以单独设置每个仓库的可见性为 私有、内部 或 公开。这一套配置下来,访问控制完全不比 GitLab 弱。
5. 迁移后必须处理的三个问题集
迁移不是复制粘贴,有些细节必须提前想清楚,下面是我们踩过的几个典型问题。
5.1 部署了 .git 目录里的路径变化
从 GitLab 迁移到 Gitea 后,原有的仓库 clone 地址 git@gitlab.example.com:group/project.git 变为 git@gitea.example.com:group/project.git。开发者本地已有的 remote 需要更新。我们当时是统一要求团队用三条命令切到新 remote:
bash复制git remote set-url origin git@gitea.example.com:group/project.git
git fetch --all
git branch -vv
如果之前有人在本地保存了旧路径的临时分支,迁移后这些分支依然保留,只是往老的 GitLab push 时可能会失败,需要尽快调整 remote。
5.2 CI 并发和 Runner 资源限制
Gitea Actions 的 Runner 使用 act_runner,它的资源占用比 GitLab Runner 小得多,但在同一台低配服务器上并发跑多个流水线仍然可能会撑爆内存。
建议在 .gitea/workflows/ 的 yml 中限制并发数,或者只让一部分任务使用 docker,其他任务直接用 runs-on: ubuntu-latest 配合 --privileged 或复用宿主机的 Docker 执行。
这里值得单独提一下,如果团队中包含大量 docker build 任务,建议直接把 Runner 挂到构建机上,并给它单独的 Docker socket 权限。如果你的 Runner 只挂在同一个低配服务器上,建议在 yml 里用 concurrency 去控制任务队列:
yaml复制concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
5.3 Git LFS 和超大文件支持
Gitea 支持 Git LFS,但是需要在 app.ini 中显式开启:
ini复制[lfs]
ENABLED = true
如果你之前 GitLab 仓库里用了 LFS,在迁移时也要复制 LFS 对象。Gitea 的迁移工具会处理常见场景,但如果 LFS 里有大量超过 1GB 的历史文件,建议考虑是否要对仓库做历史瘦身,把大文件拆到一个独立的 LFS 存储中。
我实际遇到过一种情况:项目代码 700MB,但仓库历史里的资源文件加 LFS 对象加起来有 6GB。迁移到 Gitea 后发现磁盘占用翻了 3 倍,后来用 git filter-repo 清理了历史中的无用大文件,才把仓库压到 1GB 出头。建议在迁移前先检查仓库体积。
5.4 多实例与高可用
Gitea 本身不是为高可用而设计的,但它支持通过外部数据库(MySQL/PostgreSQL)和外部 Redis 做水平扩展的缓存与队列。如果你的团队对高可用有硬性要求,可以考虑加一层负载均衡,前端 Nginx 分发到多个 Gitea 实例。
但说实话,中小团队没必要这样做。一个单实例 Gitea 配合每日备份已经能保证 99% 的可用性。我们跑了近半年,没有出现过一次因服务本身导致的中断。
6. 常见问题排查指南:从登录失败到升级方向
6.1 登录、Token 与版本兼容性问题
如果你从 GitLab 迁移过来后见到类似“login failed. check api token or gitlab version. log in via git if the version lower than 14.0”的报错,这通常不是 Gitea 的问题,而是你原有的 GitLab 服务端 API Token 仍然沿用到某个自动化脚本里。一旦域名切换到 Gitea,但脚本里的 token 还是 GitLab 生成的,自然无法通过认证。
解决方式是:在 Gitea 的用户设置中重新生成 Application Token,然后在自动化脚本中替换。
另一个常见问题是,某些开发者的 SSH key 之前添加到了 GitLab 账号,迁移后需要重新添加到 Gitea 账号下,否则 git push 时会提示权限失败。
6.2 Gitea 如何升级
Gitea 可以跨版本直接升级,不需要像 GitLab 那样按大版本逐步升级。官方在每个 release 中提供了二进制包和 Docker 镜像,替换镜像即可完成升级。
Docker 部署的升级命令:
bash复制docker compose pull gitea
docker compose up -d
升级前建议先做一次 gitea dump 备份,避免意外情况。
6.3 分支管理与权限防护
Gitea 分支保护功能在仓库设置 → 分支 中开启。可以限制哪些分支不能直接 push,必须通过 Pull Request 合并。同时还能要求必须通过 CI 检查才能合入。
热词中“关闭 guest 访问”其实就是一个关键的安全配置,建议在 Gitea 的后台管理面板中直接设置。我们还额外设置了一些受保护的分支,用于防止误操作:
code复制分支:main
规则:禁止直接 push,必须提交 PR,并要求至少 1 个 reviewer 审批。
这个功能可以帮助团队快速适应从 GitLab 到 Gitea 的审查流程差异。
6.4 常见 git 操作问题
“gitlab 要先 commit 代码,然后再 push 吗”这个问题其实和 GitLab/Gitea 无关,而是 Git 的基本操作。代码要提交到本地仓库(commit),再推送到远端(push)。这在 Gitea 上完全一样,没有额外限制。
可能有人混淆了 GitLab 编辑器中在线提交和本地 Git 提交的关系。在 Gitea 中,你也可以直接在网页上编辑文件并提交,效果等同于在本地提交并 push。但正规工作流还是建议本地 commit + push 到特性分支,再发起 PR 合并到 main 分支。
7. 最终落地效果:10GB 降到 600MB 之后
迁移完成后,我们把原先的 GitLab 服务停掉,服务器内存曲线直接掉了一个数量级。以前 4GB 内存的机器跑 GitLab 经常卡顿,现在同一台机器同时跑 Gitea、一个 Gitea Runner、Nginx 反代,还有余力跑两三个小型应用。
磁盘占用也大幅下降。GitLab 的安装目录和 PG 数据库体积很大,Gitea 加上仓库裸数据,整体从 10GB 级别降到 600MB 到 1GB 级别。团队日常开发所需的核心功能全面覆盖,代码审查、Webhook、CI/CD 自动化部署等链路均已跑通。
回头看,我们的核心决策逻辑只有一个:匹配真实的团队规模和资源成本。如果团队在几十人以内、仓库数量不多、对 DevOps 功能需求相对集中,GitLab 的能力确实超出实际场景。Gitea 的轻量设计与运维简单性反而让团队更专注在代码本身。
这里再说一个小建议:迁移初期不要急于关闭 GitLab,可以并行观察一两周。让团队在新平台上正常开发,等确认所有关键流程都稳定了再下掉 GitLab 服务。数据上的安全感有了,切换过程也会从容很多。
如果你正被 GitLab 的内存占用或升级复杂度困扰,我非常建议先拉一台小机器把 Gitea 部署起来,用一段时间,你就能直观感受到“轻量级神器”到底值不值得换。以我们目前的团队规模,这个替代方案可以继续稳定用下去。
