1. 为什么我们动了换掉 GitLab 的念头
团队里最早提这事的是后端的老张。那天他要在自己的笔记本上把公司 GitLab 的仓库拉下来改个 bug,结果等容器镜像拉完、服务起来,他先去冲了杯咖啡,回来发现还没起来。这不是段子,是我们真实遭遇。公司那台 GitLab 服务器其实配置不算差:4 核 8G,跑个中小型团队按理说绰绰有余。但实际用起来,内存长期被吃满,动不动就报错,重启一次至少五六分钟,有时候连网页端都打不开,git clone 也是时快时慢。
我们最开始以为是人多导致的,后来一排查才发现,项目仓库也不过几十个,团队也就十几个人。GitLab 本身作为一个全家桶式的 DevOps 平台,带了一堆我们用不着的东西——CI/CD、容器镜像仓库、依赖扫描、安全报表、代码质量分析,甚至还有类似 Trello 的 Issue 板。
这些功能听起来很美好,但问题在于它们全都绑在一个服务里,每一个模块都在按需吃内存、跑后台任务。哪怕你只把 GitLab 装上、不搞任何配置,它光是空载状态下就能让 4G 内存见底。在我们的机器上装完以后,镜像解压完差不多 10GB 磁盘空间没了,内存稳定占用在 4GB 以上。而我们要的核心功能无非就这几个:代码托管、分支管理、权限控制、Merge Request 审阅、简单的问题追踪。有些团队成员甚至不太用网页版操作,基本都是命令行+本地 IDE 的 Git 功能完成流程。
后来机缘巧合,我们接触到了 Gitea——一个从 Gogs 分叉出来的国产开源方案。它把自己定位成"轻量级代码托管工具",整个服务就一个二进制文件,运行时内存占用一般不超过 200MB,部署时间三分钟。我们的主力服务器上,Docker 方式跑一个实例,磁盘占用量我看了下,大概 600MB 出头。这数据放到一起对比,你根本不需要做太复杂的技术论证就能得出结论。
10GB 对 600MB,不只是数字上的差距,背后反映的是两种完全不同的产品哲学:一个是"什么都要做,但运维成本高、资源占用重",另一个是"专注核心场景,把资源效率做到极致"。如果你也想换掉自己那台跑得又慢又卡的 GitLab,那我这篇文章基本能帮你把前因后果、迁移步骤和避坑点都讲清楚。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 资源占用对比背后的原理差异
2.1 为什么 GitLab 会占用那么多资源
很多人会认为,GitLab 部署完就只是一个 Web 服务,能有多吃资源?实际拆开来看,GitLab 是一个典型的多进程、多模块架构,部署之后默认会同时拉起十几个子服务协同工作。除了核心的 Web 前端和后端 API 之外,它还包括:
- PostgreSQL 数据库,保存用户、项目、权限、Issue、Merge Request 等所有元数据。
- Redis 缓存,用于处理会话状态、后台队列和缓存热数据。
- Sidekiq 后台任务队列,专门执行异步任务,比如仓库统计、邮件通知、Webhook 触发、CI 队列处理等等。
- GitLab Workhorse,负责处理 Git HTTP 请求、文件上传下载等加速转发逻辑。
- Prometheus 监控采集器,默认会收集自身的运行指标。
- Grafana 面板,展示监控图表,即便你从来不看它,它也照样跑。
每个服务各自占用一份 CPU 和内存资源,之间还要保持网络通信和共享磁盘。所以在 Docker 部署时,GitLab 官方推荐的容器本身就是一个完整的操作系统镜像,里面打包了以上所有组件。而 Gitea 走的是另一条路——它的架构极简到不能再简:一个主进程负责 Web、Git SSH、内部数据库连接,所有功能模块都跑在同一个进程内,不拆分。
从设计初衷来比,GitLab 更像一个集成度极高的 DevOps 平台,它默认你就需要完整的研发生命周期管理功能,所以宁可资源开销大也要把所有模块预置好。而 Gitea 是典型的"够用就好"路线,它把代码托管最核心的部分做到极致,其他扩展能力全部提供可插拔的选项——比如 CI/CD 它本身不内置,而是通过和 Drone、Jenkins 这类外部工具做 Webhook 联动来实现。
这个差异直接体现到最终效果上:GitLab 在任何官方支持的部署方式下都建议至少 4GB 内存,如果是生产环境多人并发,官方推荐 8GB 起步。Gitea 官方只建议 2GB 内存即可流畅运行,实际我们用 1GB 内存的机器也能带得动十几个人的日常使用。
磁盘占用更是差距惊人。GitLab 一个 Docker 镜像的体积在 2.5GB 左右,解压后运行实例通常会占据 6GB 到 10GB 不等,这还不算上 Git 仓库自身、日志文件、依赖缓存的数据。而 Gitea 的运行时主体就一个几十 MB 的二进制文件,加上自定义配置和模板,整体装完占用不超过 500MB 到 600MB。
有人说拿它们这样对比不太公平,毕竟 GitLab 功能多太多了。但这就是现实:大部分中小团队真正高频使用的,只是其中 20% 都不到的能力。为了 20% 的功能付 100% 的资源代价,这笔账怎么算都不划算。
2.2 Gitea 的部署形态和优化思路
从部署方式来看,Gitea 足够灵活。它官方提供三种主流玩法:直接下载二进制包在物理机运行、用 Docker Compose 一键拉起、还有就是通过系统包管理器直接装(比如 apt 或 yum)。
二进制运行方式我印象最深。因为它本身就是一个可执行文件,把文件放在 /usr/local/bin 目录,配好环境变量和服务管理脚本,系统启动时由 systemd 拉起即可。它不需要 Java 运行时、不需要 Ruby、不需要 Node.js,甚至连外部数据库都可以省略——默认使用 SQLite3 嵌入式数据库,单文件存储。这意味着你可以把它装在树莓派、NAS、甚至一台跑着老版本 Ubuntu 的旧笔记本上。
我们的实际部署选择是 Docker Compose 方案。原因很简单:团队已有的服务器上跑着不少 Docker 容器,统一用 Compose 管理环境更一致,重装迁移也只需要备份一个目录即可。和 GitLab 那套庞大的容器编排参数来比,Gitea 的 Compose 配置看起来简直寒酸,但它能解决所有问题。
我需要特别说一下 Gitea 的扩展机制。它有一个名字叫"钩子"(Hook)的东西,分为 Git 钩子和 Web 钩子。比如你可以在仓库里配置 Webhook,当有人提交代码时自动触发你内网的 Jenkins 任务做一次构建。再比如配合 Drone CI,直接在 Gitea 里点击"构建状态"标签页看到流水线进度。这种"核心精简+外部扩展"的思路,既保证了基础功能的轻量,也不用牺牲功能性。
我最喜欢的一点是 Gitea 自带一个"迁移外部仓库"的功能。在创建新仓库时,你可以直接填一个远程仓库地址,比如 GitLab、GitHub、Bitbucket 等平台的已有项目链接。它会自动把仓库内的 Git 历史、分支、标签乃至 Issue 都拉取过来。这个功能在我们切到 Gitea 的时候发挥了巨大作用,后面我会细讲。
2.3 什么样的情况可以换,什么时候劝你别换
在做技术选型替换之前,先搞清楚自己的真实需求边界,比急着动手重要得多。如果你遇到下面这些情况,换成 Gitea 大概率是体验提升:
- 团队在 50 人以内,日常流程就是参考主分支、开分支开发、提 Merge Request、Code Review、合并回主分支。
- 服务器资源紧张,预算有限,不想在代码托管这件事上投入过多机器成本。
- 对 DevOps 的需求比较轻,CI/CD 已经有独立系统,或者用外部云服务在跑。
- 受够了 GitLab 频繁升级后偶发的不兼容问题,想要一个"装完即忘"的低维护方案。
- 需要在内网离线环境部署一套代码托管系统,资源占用不想太大。
但我也必须坦白说,Gitea 不是万能的,有一些场景它并不合适。比如你们团队的研发流程强依赖 GitLab 内置的 CI/CD 能力,已经写好了一堆 .gitlab-ci.yml 流水线,那迁移成本会很高。再比如你们用到了 GitLab 的代码质量门禁、安全扫描、合规审计这类企业级功能,Gitea 默认不提供,插件的成熟度可能比不上 GitLab 原生能力。
还有一点容易被忽略:GitLab 的 Issue 管理和权限系统比 Gitea 复杂很多,如果一个大型团队有着极其细粒度的权限矩阵要求,比如某个小组只能看某些项目的某些分支,Gitea 可能需要你花额外功夫通过配置来模拟实现。换句话说,Gitea 更适合"研发流程清晰、权限要求不复杂、追求效率"的团队。
一句话总结我的选型逻辑:先给团队需求做减法,用不到的功能不要让它给你增加运维负担,然后把资源投入到真正需要的地方去。
3. 完整搭建流程与关键配置解读
3.1 迁移前备份清单
在动手装 Gitea 之前,一定先确认备份做好了。很多人会匆匆忙忙开始迁移,结果中途发现问题要回退,才发现旧服务器的数据没有完整导出。如果你现在还在跑 GitLab,至少要确认以下几类数据都有备份:
- Git 仓库裸数据,通常路径类似 /var/opt/gitlab/git-data/repositories,每个项目的 .git 目录都在里面。
- 数据库数据,GitLab 的元数据都存在 PostgreSQL 中,包括用户信息、项目注册信息、权限配置、Merge Request 记录、Issue 数据等。
- 配置文件,重点是 /etc/gitlab/gitlab.rb,这里面记录了 GitLab 的所有设置,包括外部 URL、SMTP 邮件服务器配置、备份策略等。
- 上传文件,例如头像附件、项目附件等,路径一般在 /var/opt/gitlab/gitlab-rails/uploads。
如果你是直接用 Docker 跑的 GitLab,那么最简单的方案是把整个容器挂载的配置和数据目录直接打包。我们当时是这样操作的:先把 gitlab.rb、gitlab-secrets.json 这些关键配置抄下来存档,然后用 gitlab-backup create 命令生成备份压缩包,再把整个 repositories 目录做了同步拷贝到一台中转机器上。
这里要提一个我踩过的坑:只备份 Git 仓库目录是不够的。GitLab 的仓库文件在不同版本之间可能会在存储路径上有差异,而且用户和权限信息全在数据库里,光恢复裸仓库根本找不回原始的分支权限。正确做法是 Git 仓库数据与数据库都要备份,一个都不能少。实际经验是,如果你在旧的 GitLab 上配置过 SSH 公钥,光把代码 clone 下来没用,因为 SSH key 对应关系也需要理清楚。
我是基于"用 git clone --mirror 保留完整历史"的思路来实现仓库级迁移的。先确认每个仓库的远端地址和可见权限,然后用带 --mirror 参数的 clone 命令把整个仓库镜像到本地,它会把所有分支、标签、历史提交都带下来,再在 Gitea 上创建新空仓库后推上去。但这个方法只覆盖代码本身,仓库里关联的 Issue、Merge Request 记录、代码评注都不会被带过来。
如果你们对历史的工作流记录(比如去年有哪些 Merge Request、谁审核过、Issue 处理流程如何)有比较强追溯需求,建议在旧系统上把这些记录导出存档,或者干脆同时保留旧服务器一段时间做只读查询。我们就是这么干的:把 GitLab 机器保留了两周,期间任何人需要老数据都能自己上去查,两周后确认无访问才彻底关机。
3.2 使用 Docker Compose 部署 Gitea
条件允许的话,我推荐用 Docker Compose 方式部署 Gitea。虽然直接用二进制文件更快,但 Compose 方式把数据目录和配置文件都固化到了一个目录里,团队里任何人都能照着一份文档完成同样环境的搭建。
我这里贴一份我们实际在用的 Compose 配置,去掉了一些冗余注释,按照可以直接落地的状态来写:
yaml复制version: "3"
services:
gitea:
image: gitea/gitea:latest
container_name: gitea
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__server__DOMAIN=git.internal.example.com
- GITEA__server__SSH_DOMAIN=git.internal.example.com
- GITEA__server__ROOT_URL=http://git.internal.example.com:3000
- GITEA__server__HTTP_PORT=3000
- GITEA__server__SSH_PORT=2222
- GITEA__database__DB_TYPE=sqlite3
- GITEA__database__PATH=/data/gitea/gitea.db
ports:
- "3000:3000"
- "2222:22"
volumes:
- /data/gitea:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
restart: unless-stopped
看着是不是比 GitLab 的精简多了?但即便这么短,里面有几个点如果理解错了,后面会遇到很多麻烦。
首先要理解 USER_UID 和 USER_GID 的作用。Gitea 容器内部默认用 uid 1000 来运行服务,如果这个 uid 和你宿主机上 /data/gitea 目录的所有者不一致,容器就可能遇到文件权限拒绝的问题。我通常会把宿主机上 /data/gitea 目录的所有者显式改成 1000:1000,或者直接让容器以宿主机某个固定用户的 uid 启动。实际里不要图省事跳过这一步,否则后面 Git push 时经常碰到奇怪的 500 错误。
然后是端口映射。Gitea 默认的 HTTP 端口是 3000,为什么映射两个?我这里的规划是:3000 端口作为网页和 Git HTTP 克隆的入口,2222 端口作为 SSH 登录的入口。因为宿主机 22 端口很可能已经被系统 SSH 占用了,所以用了 2222 对外映射,同时容器内是 22,对用户来说拉取代码时地址会变成 git.internal.example.com:2222/owner/repo.git 这种形式。
这点在配置 SSH 时需要格外注意:如果你通过 SSH 方式 clone 代码,需要在本地 Git 配置里加上端口号,或者在 ~/.ssh/config 文件里为主机单独做一个别名并指定端口。我们团队有人第一次配置时没加端口号,一直连接失败,心态差点崩了。
再看环境变量那一串,GITEA__ 前缀后面跟的是 Gitea 配置文件 app.ini 里的节名和键名,双下划线分隔层级。比如 GITEA__server__DOMAIN 就是设置 [server] 区域中 DOMAIN 这个键。这种方式可以让你不用进容器编辑配置文件,直接通过环境变量覆盖默认值。好处是配置和部署过程都代码化、可追溯,我们保留了不同环境的 Compose 文件在 Git 仓库里,新环境搭建直接复制粘贴即可。
3.3 初始化安装和基础设置
启动容器之后,在浏览器访问 http://服务器IP:3000,第一次会进入安装引导页。Gitea 默认会在第一次访问时弹出配置表单,这里每一项都值得仔细填。
数据库类型如果机器上没有现成的 PostgreSQL 或 MySQL,可以直接选 SQLite3。这是最省事的选择,所有数据写入一个 db 文件里,方便备份,也避免了额外维护一个数据库服务。不过要提醒一句:SQLite3 更适合低并发的场景。如果你预估团队人数超过 50 人、并发推送频繁,那从一开始就选用 PostgreSQL 更稳妥。Gitea 支持 MySQL 和 PostgreSQL,只需要在同一个 Docker 网络里再加一个数据库容器,然后在表单里填上对应的连接信息。
站点名称根据公司或团队内部习惯填写,无所谓对错,但这个名称会出现在系统发出的邮件通知和页面标题上,建议设置得正式一些。仓库根目录默认是 /data/git/repositories,所有代码仓库都会存在这里,建议不要乱改,保持默认后续备份路径清晰。
SSH 服务端口这里我特别提醒一下:如果你的客户端是通过域名加端口访问 SSH,比如 ssh://git@git.internal.example.com:2222/owner/repo.git,那表单里的"SSH 服务器端口"一定要填 2222,也就是容器对外映射的那个端口。如果你填 22,Gitea 生成的 SSH clone 地址就是 git@git.internal.example.com:22/owner/repo.git,用户根本无法连接。
管理员账号建议在安装页面直接创建,不要跳过这一步。如果跳过了,安装完成之后所有用户都需要靠自己注册,一旦开启了"禁止注册"开关,你反而没有管理员账号可以用了,还得手动进数据库添加管理员,相当麻烦。
安装完成后,先进管理后台看一眼配置。在"站点管理 → 配置"里,主要检查邮件通知设置。如果 SMTP 没配好,新用户注册、找回密码、仓库动态通知邮件都发不出去,你可能会在某个新同事入职时才发现"每个人都得手动创建账号"这种窘境。我建议 SMTP 配置用环境变量的方式提前写好,而不是依赖安装页面去填,这样迁移到其他环境也不至于遗漏。
SMTP 配置需要用到端口和加密方式,这里给出一份我们环境里验证过的配置示例:
ini复制[mailer]
ENABLED = true
HOST = smtp.example.com:465
FROM = git@example.com
USER = git@example.com
PASSWD = your-smtp-password
PROTOCOL = smtps
如果你用的是 587 端口且要求 STARTTLS,那 PROTOCOL 要改成 smtp+starttls。这两个别搞混,搞混了邮件发不出去,而且 Gitea 报错信息有时候并不是直观地写"邮件配置错误",而是生硬地提示"SMTP connection failed"。我第一次踩这个坑时排查了半天,最后才发现是协议类型不匹配的问题。
3.4 仓库迁移:从 GitLab 批量搬迁到 Gitea
仓库迁移是整件事的核心,这个环节处理得好不好,直接决定团队成员对"换系统"这件事的体感评价。我归纳了两套方案,大家可以根据自己的实际环境选择。
方案一(推荐用于仓库数量多但历史 Issue 不重要的情况):利用 Gitea 自带的"迁移仓库"功能。在网页端右上角点"+"号,选择"迁移外部仓库",填写 GitLab 仓库的地址和访问 Token 或者用户名密码。Gitea 会自动从远端拉取仓库信息和 Git 历史。实测下来,一个 1.5GB 左右的仓库,带宽正常的情况下大约需要十分钟,完整保留所有历史提交和标签。
需要注意的一个坑是:如果 GitLab 开启了 2FA 双重验证,你需要到 GitLab 用户设置里单独生成一个 Personal Access Token,而不是用账号密码来填。Token 权限要至少包含 read_repository 和 read_api,否则迁移过程会报错。
方案二(适合仓库数量少,或者想要手动控制迁移过程):在本地用 git clone --mirror 做中转。步骤如下:
bash复制git clone --mirror http://gitlab旧地址/group/project.git
cd project.git
git remote set-url origin http://gitea新地址/group/project.git
git push --mirror origin
--mirror 会把远端所有 refs(包括分支和标签)打包成镜像,这样推送到新仓库后历史完全一致,不会有丢失。但注意 --mirror 克隆出来的是一个裸仓库,没有工作区文件,你不需要进去改代码,只做中转用途即可。
迁移完第一批仓库后,先别急着通知所有人改地址。我建议先在 Gitea 上新建一个测试项目,让两三个成员试着 clone、push、提一次 Merge Request,把整个流程走通,确认权限配置和分支保护规则都符合预期,再批量通知所有人完成切换。
我们切换时还做了一个细节:保留旧 GitLab 的访问权限,同时在 Gitea 上开放了"只读镜像"模式。这样团队老成员不会因为推送到了旧地址而产生数据不一致的问题,也给想慢慢迁移的人留了缓冲时间。两周后旧服务器数据彻底不活跃了,我们再从运维角度把它清理掉。
3.5 用户体系和权限迁移
用户是仓库系统里最容易迁移出问题的部分。尽量别手动一个个创建,那样既慢又不安全。Gitea 支持从文件批量导入用户,也支持通过网站管理后台逐个添加。
如果你是公司内部部署、已经接入了 LDAP 或 OAuth 登录,那最简单的方式是直接在 Gitea 的认证源设置里加一个 LDAP 服务。这样用户首次访问时直接用公司域账号密码登录,系统会自动创建对应的 Gitea 账户,不需要提前批量创建任何账号。这个体验对内部团队来说是很好的,尤其省去了密码找回的麻烦。
我们换到 Gitea 时还没有 LDAP,当时全是手动创建。几十个账号还真花了些时间。后来临时加了一个批量导入脚本,用 Gitea 提供的 API 按团队成员名单批量调用创建用户接口,效率高很多。如果你也是管理员,可以直接到后台→认证管理里添加认证源,而不必走网页注册流程。
权限模型上,Gitea 和 GitLab 很不一样。Gitea 默认是仓库级别的权限控制,一个项目分为 Owner、Admin、Write、Read 四种角色。团队可以按项目组(Organization)来组织,类似 GitLab 里的 Group。先建好组织,再把成员分配到组织里,然后给组织统一分配仓库权限,比单独给每个仓库添加成员要轻松得多。
从 GitLab 带过来的习惯在这里需要调整一下:GitLab 的权限层级很细,可以做到单一项目里的某个用户只能看到某些分支。Gitea 的分支保护规则解决的是"谁能推送、谁必须走 MR 流程"的问题,而不是解决"谁能看到分支"的可见性问题。如果你们真的有那种需要隐藏某个分支内容的敏感场景,建议用独立仓库来隔离,而不是试图在同一个仓库内做隐藏。
4. 实际使用体验中的关键功能拆解
4.1 分支保护策略与 Merge Request 流程
我们团队换到 Gitea 之后,日常协作流程基本保持和之前 GitLab 时期一致。主干分支 main 受到保护,普通成员没有直接推送权限。开发人员在 feature 分支上提交,然后到网页端发起 Pull Request(注意 Gitea 中叫 Pull Request,GitLab 中叫 Merge Request,本质相同)。
在设置分支保护时,Gitea 的规则配置界面相对简洁。我通常设置以下规则:
- 启用"阻止推送"和"阻止移除分支"。
- 勾选"需要 Pull Request 才能合并"。
- 合并前至少需要 1 个批准。
- 禁用"删除合并后的分支"。(这个看团队习惯,有人喜欢合并时顺手删分支,我们通常保留一段时间)
对于还在纠结"必须先 commit 再 push 吗"的新手,我多说一句:代码先保存在本地仓库(commit),再把本地的提交记录推送到远程(push)。commit 是提交到本地,push 是同步到服务器。这个操作顺序在 Git 里面从没变过,和用 GitLab 还是 Gitea 没有任何关系。
Gitea 的 Pull Request 页面体验比我预期中要清爽,对比视图、评论行内提示、审查批准这些基础功能都有。它没有 GitLab 那么复杂的"多层审批流",但对大多数技术团队来说,一个 approve 加自动合并已经足够。如果你需要代码质量工具介入(比如 SonarQube),也可以配置 Webhook 让它在合并请求创建时自动触发外部检查。
4.2 与 Jenkins 这类 CI 工具的联动细节
虽然 Gitea 自己不集成 CI,但它是优秀的触发器提供方。在仓库的"设置 → Web 钩子"里,可以添加一个 Gitea 类型的 Webhook,选择触发事件:分支推送、Pull Request 创建、合并、Tag 推送等。把这些事件以 POST 请求方式发给你自己的 CI 服务。
我们团队的走法是 Gitea 配 Webhook → 触发 Jenkins 任务。Jenkins 侧装一个 Generic Webhook Trigger 插件,然后解析请求体里的仓库名和分支名,构建参数从那里面提取。
实际联动配置中可以这样操作:
- 在 Jenkins 系统配置里新建一个 Pipeline 任务,勾选"Generic Webhook Trigger"。
- Token 那里填一个随机字符串,比如 BUILD_TRIGGER_TOKEN。
- 在请求体里通过 JSONPath 提取仓库名和分支名,Gitea 的 webhook 请求体很长,但字段齐全,直接取出即可。
- 在 Gitea 仓库 Webhook 设置里填上:http://jenkins服务器/generic-webhook-trigger/invoke?token=BUILD_TRIGGER_TOKEN
我建议在配置完以后,先在 Gitea 仓库里点"测试推送"按钮,从 Webhook 配置页发起一次测试事件,然后立刻去 Jenkins 看构建是否被触发。这一步能避开很多 URL 拼错或者 Token 不匹配的低级问题。
如果你是追求极简的方式,Gitea 还支持在提交信息里带 [skip ci] 字样来跳过外部 CI。这个功能对我们"改个错别字也触发一次构建"的场景很实用。具体语法看你在用的 CI 工具,常见的 Jenkins 本身也会识别某些模式来跳过构建,但 Gitea 在 Webhook 请求体中会带提交信息,所以你的 CI 服务可以在 job 开头判断提交信息是否需要跳过。
4.3 SSH 配置与在 VS Code 里拉取项目的连贯操作
很多热搜词里都有 "vscode从gitlab拉项目到本地"、 "gitlab配置ssh密钥" 这类问题,说明大量用户卡在远程拉代码的第一步。我专门说一下 Gitea 下的 SSH 配置流程,看完基本可以举一反三。
先在本地生成一对 SSH 密钥,如果之前已经生成过就不要覆盖原来的,可以直接用默认的 id_rsa,也可以用专门给 Gitea 建一把新钥匙,比如 id_ed25519(更安全,长度也更短)。
bash复制ssh-keygen -t ed25519 -C "you@example.com"
生成完成后,把公钥内容复制到 Gitea 个人设置 → SSH / GPG 密钥 → 添加密钥即可。这里有点和 GitLab 不同的细节:Gitea 支持把一个公钥绑定到不同用户上吗?不支持,同一把公钥你只能添加一次。如果你以前用同一把公钥在多台服务器上使用,添加后不会冲突,但如果你把同一公钥添加到另一个 Gitea 账号上,会被直接拒绝。
本地测试 SSH 连通性用这条命令:
bash复制ssh -T git@git.internal.example.com -p 2222
看到返回信息里有 Hi xxx,表示连接成功。这时候你再打开 VS Code,在源代码管理面板点击"克隆仓库",粘贴 HTTP 或 SSH 地址就能拉下来了。
实际工作中我遇到最多的问题是"能 clone 但不能 push"。这多半是远程地址里带的是 http 协议而 Gitea 的 HTTP 推送限制没开,或者 SSH 密钥没配对。如果确定是 HTTP 方式还能正常操作但 SSH 不行,那就按上面的步骤重新检查密钥关联和端口配置。确认问题的最快方法是直接看 Gitea 的运行日志,路径在 Docker volume 的 /data/gitea/log 目录,搜索 error 相关的记录,很多时候原因一目了然。
5. 从 GitLab 迁移到 Gitea 的常见问题与排查实录
5.1 登录失败与 Token 失效问题
迁移后最频繁的问题,一是团队成员说网页登录不上,二是某些自动化脚本在调用 API 时报错。第一种情况多半是密码策略导致的——如果你从 GitLab 把用户密码哈希导过来,Gitea 未必兼容,所以我更推荐迁移后让用户走重置密码流程,或者用管理员后台改一次初始密码再通知本人修改。
第二种情况,登录报错信息很典型:login failed. check api token or gitlab version. log in via git if the version is old。这个主要是因为脚本里习惯性填了 GitLab 的 Personal Access Token,但 Gitea 并不认这种旧格式的 Token。解决办法是到 Gitea 用户设置 → 应用 → 生成新令牌,生成的 Token 以 gitea_ 开头,API 请求里把它填到 Authorization 请求头中或者按照不同工具的提示放到对应的 Token 配置项里即可。
另外有关 422 登陆错误的问题,这个在 Gitea 上不一定常见,但如果你的反向代理层做了解析,而且转发的 Host 头不对,可能导致 CSRF 校验失败。一个稳妥方案是在 Compose 环境变量里把 ROOT_URL 设为用户实际访问的地址,不要在它和 Nginx 转发目标之间留下不一致。
隐身模式可以登陆通常在浏览器场景下被提到,这是因为旧登录态 Cookie 冲突。经历迁移后本地浏览器缓存着旧 GitLab 的 Cookie,新的 Gitea 域名如果设置成相同的访问地址,Cookie 就可能互相覆盖。开隐身窗口或者清掉站点 Cookie 后问题自然消失。
5.2 仓库恢复中遇到"无权限"问题
Gitea 官方文档里说可用 gitea dump 命令做备份,但实际恢复时,最怕遇到文件权限缺失。比如你解压备份文件之后,目录的所有者是 root,而 Gitea 容器内部是以 uid 1000 的身份去读文件,权限不足时会直接拒绝访问。
如果你在 restore 时遇到报"无权限"的问题,最快的解决方式是确认整个 data 目录的所有者:
bash复制chown -R 1000:1000 /data/gitea
然后再重启容器。要把这个动作养成习惯,每次迁移部署,第一步先调整目录所有者,再启动服务。顺序反了,容器第一次可能正常,但后续写数据时必然出问题。
5.3 高内存占用问题是否还存在
很多人一看到标题里的 10GB、600MB,第一反应是"换了 Gitea 是不是再也不用管内存了"。这个期待过于乐观。
Gitea 的常态内存占用确实很低,但如果产生了大量并发任务——比如多个仓库同时被扫描、大批量 Webhook 触发、仓库非常大且大量人同时 clone——它同样会短暂飙升。好在 Gitea 不会像 GitLab 那样在空闲时依然维持一堆后台服务常驻,因此整体压力和解脱感是非常直观的。
如果你部署后观察到 Gitea 的内存占用一直居高不下,我建议看一眼是否开了某个仓库的"仓库镜像同步"功能,大型仓库的定时同步会占资源。再有就是日志目录是否有持续增长的 debug 日志在写盘。正常情况下我们的 Gitea 容器从启动后稳定运行三个月,内存维持在 300MB 上下,没有任何告警。
5.4 常见问题速查表
为了方便团队同学排查日常问题,我做了一份速查表,现在也贴出来给大家参考:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| push/pull 提示 SSH 连接失败 | 客户端访问端口和容器映射不一致 | 检查 ssh 配置端口是否为 2222 |
| 网页能登录但 Git 操作 401 | 密码和 SSH 密钥未正确配置 | Git 远端地址使用完整用户名,检查凭据管理器 |
| Gitea 容器启动后页面 502 | 端口映射冲突或目录权限不对 | 调整宿主机端口,chown 目录并重启容器 |
| 新建仓库后主页看不到 | 仓库创建到了组织项目下而非个人名下 | 确认仓库所属 Owner 是否为期望组织 |
| 分支删除后还能看到本地记录 | 本地没有执行垃圾回收且远程删除未同步 | Git fetch --prune 执行远端清理 |
| 系统邮件通知无法发出 | SMTP 协议类型填错 | 按端口使用 smtps 或 smtp+starttls |
| Webhook 推送报 401 | 请求里的 Token 没有带对 | 在 Jenkins 侧查看触发记录确认请求头 |
| 迁移后仓库统计和贡献图为空 | 提交记录里的邮箱和 Gitea 账号邮箱不一致 | 修改 Git 配置 user.email 并重新关联邮箱 |
| 恢复备份后部分仓库缺失 | 备份拿的不完整,忽略了大文件存储目录 | 确认备份文件包含 data/ 下所有子目录 |
5.5 几个容易被忽略的数据一致性提醒
在实际运维中,仓库数据文件和数据库的一致性非常重要。我在换系统时曾经只同步了仓库的 Git 数据目录,却忘了 SQLite 的 db 文件,结果新系统启动后仓库列表一片空白。数据库才是仓库的"索引表",而文件目录只是数据本体,两个地方都完整备份后,恢复起来才是健全状态。
定期做 Gitea 全量备份其实很简单:停掉容器,把整个 /data 目录打包归档即可。因为数据文件看起来很大,但实际以我们的使用规模,整个目录也不到 2GB。考虑到仓库是公司最核心的数字资产,建议把备份文件自动同步到一个独立的存储节点或云盘,每周一次全量是可接受的最低频率。
还有一点提醒:如果你们团队已经有人开始在 Gitea 上更新代码,就不要再改 Git 提交者的邮箱和用户名。Git 历史中记录的作者信息在本地仓库不会再自动改写,如果改回来再看 Gitea 的统计面板,历史记录会呈现不全。这个"无法统计推送代码量"的问题不是系统 bug,而是 Git 的提交者身份和 Gitea 账号之间没有正确关联导致的。
6. 我们在这一轮迁移里的最终心得
如果你现在还在犹豫要不要换,我分享一下我们团队的实际收获。
原来 GitLab 那台服务器每天被各种后台任务折磨得不行,每次开会前有人 push 大分支,马上有人反馈"系统变卡了"。而现在 Gitea 在同样的机器上稳稳运行着,CPU 占用常年 5% 以下,内存开销小到几乎忘了它的存在。之前 GitLab 升级一次我们得预留半个工作日处理兼容问题,Gitea 的升级就是换一个镜像重启一下,三分钟结束。
但我想强调一个更根本的变化:把代码托管工具做轻之后,我们反而更容易把精力聚焦在代码和流程上。GitLab 全家桶里的功能大多放在设置页面里吃灰,而 Gitea 的简洁让我们重新梳理了自己真正需要的规则:谁可以推送主分支、什么情况下必须走 PR、CI 什么时候触发。这些规则哪怕在旧系统上也都能配置,只是以前被繁多功能淹没,现在变得一目了然。
最后再分享一个还没提到的小技巧:Gitea 的界面上有一个"探索"页面,默认会列出所有公开项目。如果你们是内部使用,一定记得在配置里把"禁止用户自我注册"打开,同时把项目的可见性设置为"私有"或"内部"。不然别人一扫你的服务器 IP 就能看到团队的项目列表,这在真实网络环境下是很危险的信息泄露点。我们用 Gitea 替换完 GitLab 之后,真正实现了代码托管这件事上的"安逸"——它不像过往那些重型系统需要你时时照顾,更像一个踏实、省心的仓库管理员,能让你专注于更重要的事。
