自托管 Git 服务器怎么选:Forgejo、Gitea 还是 cgit
比较裸仓库 SSH、cgit、Forgejo、Gitea 和 GitLab 的内存需求,说明 1 GB VPS 能运行哪些方案,以及 Gitea 的 2 GB RAM 基线。
应运行哪种自托管 Git 服务器
自托管 Git 服务器并不是单一产品。VPS 的 RAM(随机存取存储器)决定了您可以运行哪种方案。在最小型的 VPS 上,Git 不需要专用 daemon:裸仓库加上一个 SSH(安全 Shell)账户,就已经是可用的服务器。除此之外,您还需要在 Git 旁边运行 Web 应用。每提升一个层级,都会增加小型 VPS 可能无法提供的内存需求。
共有4个层级。通过 SSH 提供裸仓库,不运行任何原本未运行的监听服务。cgit 是快速的只读 Web 查看器,不需要数据库。Forgejo 或 Gitea 是完整的代码托管平台,提供账户、问题和拉取请求,仅需几百 MB 内存。GitLab 则要求服务器的配置远高于其他方案。
根据您需要完成的工作选择方案,然后将内存需求与您付费购买的套餐进行核对。
每个选项实际需要多少 RAM
这些项目中只有两个公布了硬件配置。应将公布的配置视为最低要求,而不是保证值。实例运行后,使用 systemd-cgtop 或 ps -o rss= -C forgejo 测量实际占用情况。
此图表背后的数据
[
{
"label": "Gitea, small team",
"ram_gb": 1
},
{
"label": "GitLab, memory constrained",
"ram_gb": 8
},
{
"label": "GitLab, single node baseline",
"ram_gb": 16
}
]Gitea 文档说明,1 GB RAM 和 2 个 CPU 核心通常足以支持小型团队和项目,并指出 Raspberry Pi 3 足以应对小型工作负载。GitLab 文档将 16 GB 作为单节点安装的基线,并将 8 GB 作为其页面所称的内存受限环境的最低配置。Forgejo 完全没有公布硬件要求。它是 Gitea 的分支项目,运行方式也与 Gitea 相同,因此 Gitea 的配置是目前最接近的参考值。
在 1 GB VPS 上,这意味着:裸仓库和 cgit 可以正常运行,并且仍有剩余资源,因为二者都不会运行常驻服务。Forgejo 或 Gitea 可以启动,也能通过 SQLite 为小型团队提供服务,但此时已处于文档所列的最低配置。因此,不要在该主机上运行 PostgreSQL 和 CI(持续集成)运行器。如果 Web 界面无错误地消失,请运行 sudo dmesg -T | grep -Ei 'oom|out of memory|killed process',并查找类似 Out of memory: Killed process 1181 (forgejo) 的日志行。这表示内核的 out-of-memory killer 终止了该进程。GitLab 在 1 GB 主机上无法通过调优解决资源问题。它无法运行。
第 0 层:通过 SSH 使用裸仓库
Git 没有需要启动的网络守护进程。git push 通过 SSH 在远端以普通 Unix 进程运行 git-receive-pack,因此,任何可以使用密钥访问的账户都已经是 Git 远程端。为仓库创建一个专用账户,并将仓库放在该账户的主目录之外,因为在 Ubuntu 24.04 中,新建主目录的模式为 0750,之后添加的 Web 查看器无法读取其中的内容。
sudo adduser --system --shell /bin/bash --gecos 'Git Version Control' \
--group --disabled-password --home /home/git git
sudo install -d -m 0755 -o git -g git /srv/git
sudo -u git git init --bare /srv/git/project.git--bare 创建一个没有工作副本的仓库,这正是服务器保存的仓库类型。向包含工作副本的仓库的已检出分支推送时,默认会因 refusing to update checked out branch: refs/heads/main 被拒绝,这是该层级最常见的错误。
现在为该账户配置密钥并克隆仓库。
sudo -u git install -d -m 700 /home/git/.ssh
sudo -u git tee -a /home/git/.ssh/authorized_keys <<'EOF'
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIexamplekeyhere alice@laptop
EOF
sudo -u git chmod 600 /home/git/.ssh/authorized_keysgit remote add origin git@vps.example.com:/srv/git/project.git
git push -u origin main成功完成的首次推送会以 * [new branch] main -> main 结束。以 git@vps.example.com: Permission denied (publickey) 结束的推送从未通过身份验证,因此请使用 sudo journalctl -u ssh -n 20 读取服务器日志。出现 Authentication refused: bad ownership or modes for file /home/git/.ssh/authorized_keys 的日志行表示文件模式不正确,因为 sshd 会忽略其他用户可写的密钥文件。
然后移除该账户的 shell。
command -v git-shell | sudo tee -a /etc/shells
sudo chsh -s "$(command -v git-shell)" gitgit-shell 只接受 Git 通过 SSH 发送的少数命令,因此现在进行交互式登录时会显示消息,而不是进入提示符:
fatal: Interactive git shell is not enabled.
hint: ~/git-shell-commands should exist and have read and execute access.这就是完整的服务器。不需要数据库,也没有需要升级的 Web 进程。你放弃的是 forge 提供的所有功能:无法浏览代码、没有 issue tracker、没有 pull request,也没有按用户设置权限。该文件中的每个密钥都可以读写 git 用户拥有的所有仓库。
第 1 层:cgit 无需数据库即可提供 Web 视图
cgit 是用 C 编写的 CGI(通用网关接口)程序。Web 服务器每次请求运行一次 cgit。cgit 直接从磁盘读取仓库,不保存自身状态。Ubuntu 24.04 在 universe 组件中提供该软件包。
sudo apt update
sudo apt install -y cgit fcgiwrap nginx
sudo install -d -o www-data -g www-data /var/cache/cgit在 /etc/cgitrc 中将其指向仓库目录:
root-title=Git on example.com
css=/cgit.css
logo=/cgit.png
cache-size=1000
cache-root=/var/cache/cgit
snapshots=tar.gz zip
scan-path=/srv/gitscan-path 会遍历该目录,并列出找到的每个仓库,因此新建的裸仓库无需额外配置即可显示。cache-size 表示缓存页面的数量;该值为 0 时不启用缓存。在添加配置行之前,先查看软件包已经写入 /etc/cgitrc 的内容,因为 Debian 和 Ubuntu 软件包本身带有一些默认配置。
每个条目都会显示仓库 description 文件的第一行,因此新建的裸仓库会显示为 Unnamed repository; edit this file 'description' to name the repository.。请为每个仓库修正一次:
echo 'Project X, internal tooling' | sudo -u git tee /srv/git/project.git/descriptionnginx 站点文件及检查方法
server {
listen 80;
server_name git.example.com;
root /usr/share/cgit;
try_files $uri @cgit;
location @cgit {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi;
fastcgi_param PATH_INFO $uri;
fastcgi_param QUERY_STRING $args;
fastcgi_param HTTP_HOST $server_name;
fastcgi_pass unix:/run/fcgiwrap.socket;
}
}sudo systemctl enable --now fcgiwrap.socket
sudo nginx -t && sudo systemctl reload nginx
systemctl show fcgiwrap.socket -p Listenroot /usr/share/cgit 将 cgit.css 和 cgit.png 作为普通文件提供,try_files 则将其他请求交给 /usr/lib/cgit/cgit.cgi 处理。若出现 502 页面,且 connect() to unix:/run/fcgiwrap.socket failed (2: No such file or directory) 位于 /var/log/nginx/error.log 中,说明 socket 单元未运行,或正在监听其他路径。systemctl show 会输出它实际使用的路径。
在基于 cgit 构建服务前,需要了解两个限制。cgit 只读且没有登录功能,因此 scan-path 下的所有内容都是公开的:不要将私有仓库放在该目录中,或者在整个站点前启用 HTTP basic authentication。CGI 以 Web 服务器用户身份运行,因此该用户必须能够遍历 /srv/git 并读取每个仓库。若该用户无法进入某个目录,该目录会在索引中显示为空,而不是返回错误。
第 2 层:使用 Forgejo 或 Gitea 管理 issue 和拉取请求
Forgejo 和 Gitea 的定位相同:使用一个 Go 二进制文件提供 Web 代码托管平台,包含用户、组织、issue、拉取请求、发行版、软件包注册表和内置 CI 系统。安装只需要二进制文件和 SQLite,因此可以运行在 GitLab 无法使用的硬件上。下面的 Compose 文件来自 Forgejo 文档,镜像标签为截至 October 2026 文档中指定的版本。
networks:
forgejo:
external: false
services:
server:
image: codeberg.org/forgejo/forgejo:16
container_name: forgejo
environment:
- USER_UID=1000
- USER_GID=1000
restart: always
networks:
- forgejo
volumes:
- ./forgejo:/data
- /etc/localtime:/etc/localtime:ro
ports:
- '3000:3000'
- '222:22'docker compose up -d
docker compose ps
curl -sI http://127.0.0.1:3000 | head -1curl 命令应输出 HTTP 状态行。在完成首次运行设置前,它可能会重定向到 /install,这仍然表示服务已正常运行。如果容器直接退出,通常原因是所有权设置错误:./forgejo 目录必须属于 USER_UID 中的 UID(用户 ID),否则进程无法写入自己的数据目录。VPS 上的 Docker Compose 详细介绍了该文件布局和卷所有权规则。
设置页面上的两个选项决定克隆 URL 是否可用。SSH 端口必须是 222,因为 Compose 文件会将主机的 222 端口映射到容器的 22 端口;域名必须填写用户实际输入的名称。任意一项填写错误,所有仓库页面都会提供对复制该命令的用户均无效的克隆命令。之后,这两个值都位于 app.ini 的 [server] 部分中,分别对应 SSH_PORT、SSH_DOMAIN 和 ROOT_URL。
对于公网实例,只将 Web 端口发布到回环地址('127.0.0.1:3000:3000'),并在前面使用 nginx 处理 TLS(传输层安全)。Gitea 也可以使用其文档采用的 docker.gitea.com/gitea 镜像以相同方式安装,或安装为单个二进制文件,并配置一个 systemd unit 和一个 app.ini。当前稳定版本为 v28.1.0,于 6 October 2026 发布。项目在 September 末发布 v28.0.0 时移除了旧的 1. 前缀,因此 1.27 之后的版本是 28,而不是 1.28。
尽可能使用 SQLite。这样实例只需运行一个进程并维护一个文件,重启后也无需额外管理其他服务。多人同时写入时,PostgreSQL 才值得增加其运维成本,因为 SQLite 会串行处理写入,而较长的 CI 运行会持续写入。两个项目都支持稍后将现有实例迁移到 PostgreSQL,因此现在不必做出无法更改的决定。
Forgejo 与 Gitea:实际有哪些差异
两者有共同的历史渊源。Gitea 于 2016 年从 Gogs 分支而来。2022 年末,Gitea 的域名和商标控制权转移给了 Gitea Ltd。一些维护者随后与 Codeberg 共同启动了 Forgejo。Forgejo 由 Codeberg e.V. 发布。该组织是在德国注册的非营利协会。Forgejo 于 2024 年将许可证从 MIT 更换为 GPLv3(GNU 通用公共许可证第 3 版)。Gitea 仍采用 MIT 许可证,并在商业力量支持下开发。
日常使用中,两者的功能集相近。但两者之间的迁移路径不同。2025 年 1 月发布的 Forgejo v10.0 是最后一个可以直接使用 Gitea 数据库的版本,而且仅支持 Gitea v1.22 或更早版本。截至 2026 年 10 月,Gitea 已是 v28.1.0,因此当前的 Gitea 实例没有受支持的原地切换方式迁移到 Forgejo。请在填充数据前选定其中一个。之后如需迁移,应按导出再重新导入处理。
选择时可以遵循一个简单原则。如果您重视治理方式,或希望项目由非营利组织维护,请运行 Forgejo。如果您希望使用规模更大的安装基础,并需要商业支持选项,请运行 Gitea。两者都以开放方式维护,并且经常发布新版本:Forgejo 每 3 个月发布一个稳定版本,每年发布一个 LTS(长期支持)版本。截至 2026 年 10 月,当前版本为 v16.0.5,LTS 版本为 v15.0.9。Forgejo 对 v16 的支持持续到 29 October 2026,对 v15 的支持持续到 15 July 2027。因此,在固定主版本前,请查看其发布页面。
第 3 层:GitLab 在执行任何操作前的资源成本
GitLab CE 属于另一类软件。一个实例由多个相互协作的服务组成:用于 Web 应用的 Puma、用于后台作业的 Sidekiq、PostgreSQL、Redis、用于访问代码仓库的 Gitaly,以及前置的 nginx。Omnibus 软件包会将这些组件一起安装,因此安装过程简单,但内存最低需求较高。
GitLab 的需求页面将单节点安装的基线设为 16 GB RAM 和 8 vCPU,并将 8 GB 列为内存受限环境中的最低配置。该页面还要求禁用 swap,因为高负载下发生交换会严重降低实例性能。这些是截至 October 2026 发布的数值,而且多年来一直在上升,因此在规划服务器配置前应再次查看该页面。
在这个资源预算下,您可以获得实际功能:容器注册表、软件包注册表、细粒度权限、合规与审计功能,以及经过大规模测试的 CI。如果团队中没有人能说出本季度需要上述功能中的任何一项,那么您花钱购买的只是更大的 VPS,却没有获得实际收益。
SSH 访问模型:一个 git 用户和多个密钥
这里的每个层级都使用相同的身份验证方式。系统中只有一个名为 git 的 Unix 账户,所有公钥都写入该账户的 ~/.ssh/authorized_keys。密钥用于身份验证。同一行中写在密钥前面的选项决定授权范围。
普通密钥行会授予持有者该账户所能执行的全部操作。强制命令可以将权限限制为 Git:
restrict,command="git-shell -c \"$SSH_ORIGINAL_COMMAND\"" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIexamplekeyhere alice@laptoprestrict 自 OpenSSH 7.2 起可用,只需一个选项即可关闭端口转发、代理转发、X11 转发和 PTY(伪终端)分配。command= 会用你指定的命令替换客户端请求的命令。Git 仍可正常工作,因为 Git 会在 $SSH_ORIGINAL_COMMAND 中发送请求。
代码托管平台会为你写入这个文件。这正是 tier 0 与 tier 2 的实际区别。当 Forgejo 和 Gitea 使用系统 OpenSSH 服务器(这是默认方式)时,它们会重写 authorized_keys,为每个已注册密钥写入一行,并在每行中加入一个强制命令,通过数据库 ID 指定该密钥:
command="/usr/local/bin/forgejo --config=/etc/forgejo/app.ini serv key-3",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIexamplekeyhere alice强制命令使一个共享的 Unix 账户能够实现按用户授权:key-3 告诉代码托管平台当前连接的是哪个用户,平台会在传输任何对象前检查该用户是否有权访问仓库。不要在由代码托管平台管理的服务器上手动编辑该文件,因为平台会根据数据库重新生成文件,你添加的行会消失。如果启用代码托管平台内置的 SSH 服务器,或者让 OpenSSH 通过 AuthorizedKeysCommand 向代码托管平台请求密钥,平台就完全不需要该文件;在后一种情况下,Forgejo 和 Gitea 都要求关闭该文件的生成。部署密钥使用相同的机制:部署密钥是注册到单个仓库的普通 SSH 密钥,通常为只读权限,权限检查由代码托管平台而不是 sshd 执行。
有两个习惯比上述任何配置都重要。每个人或每台机器使用一个独立密钥,绝不要共享密钥,因为撤销共享密钥意味着必须同时为所有人轮换密钥。有人离开当天就删除其密钥,因为文件中的旧密钥会形成永久登录入口,而且无人监控。服务器上的良好 SSH 密钥管理介绍了密钥类型和密码短语,这些内容在这里同样适用。如果服务器是新的,应先阅读 新 VPS 上线后的前十分钟,再将仓库放到服务器上。
我可以在自己的 Git 服务器上运行 GitHub Actions 吗?
你可以运行使用 GitHub Actions 语法编写的工作流,但不能运行 GitHub。Forgejo 从 v1.21 开始默认启用 Forgejo Actions,并从每个仓库中的 .forgejo/workflows 读取工作流文件。Gitea Actions 的工作方式相同,并读取 .gitea/workflows。两者都需要单独安装 runner,并使用管理员设置中的令牌将其注册到你的实例。许多已发布的 action 无需修改即可运行;但调用 GitHub API 或依赖 GitHub 托管基础设施的 action 无法运行。
需要考虑两个后果。使用常见的容器标签时,runner 会为每个作业启动一个容器,因此需要独立的容器引擎和内存预算。这也是不应将它与 forge 放在同一台 1 GB 服务器上的原因。两个 runner 也可以通过 Forgejo 中以 :host 结尾的标签直接在主机上运行作业。此方式不需要容器引擎,但作业与该主机之间也没有容器隔离。runner 会执行工作流文件指定的任何内容。Forgejo 文档对此有明确说明:runner 会执行远程代码。条件允许时,应为它分配独立主机;至少也应使用独立的非特权用户,并将注册令牌限制在一个仓库内。
如果你的仓库仍保留在 GitHub,而你只想使用自己控制的硬件提供计算资源,这是另一种配置,步骤也不同:GitHub Actions 自托管 runner 会连接到 GitHub 仓库,不需要上述配置。如果你仍在评估离开 GitHub 的代价,GitHub 实际提供的功能 会将 Git 托管服务与其周边网络服务区分开来。
备份:仓库只包含一半状态
裸仓库是一个目录,因此复制该目录就会复制其中的全部内容。从另一台机器执行镜像克隆才是真正的备份,并且可以在原位置刷新:
git clone --mirror git@vps.example.com:/srv/git/project.git
cd project.git && git remote update该命令会拉取每个 ref 和每个对象。但它不会拉取服务器端 hook 或 description 文件,因此如果使用 hook,还应保留该目录的文件级副本。
代码托管平台会将 issue、pull request、用户、密钥和权限保存在数据库中,仅复制仓库会丢失所有这些数据。两个项目都提供了 dump 命令,可将数据库、仓库、配置和附件写入同一个归档文件:
sudo -u git forgejo dump -c /etc/forgejo/app.ini -f /var/backups/forgejo-dump.zip在 Docker 中,同一命令需要在容器内运行。配置路径取决于镜像,因此执行前先确认路径:
docker compose exec server ls /data/gitea/conf
docker compose exec -u git server forgejo dump -c /data/gitea/conf/app.ini使用数据所有者的身份运行该命令,并将归档文件写入该用户有权限写入的目录。然后将归档文件复制到服务器之外,因为只存在于被备份机器上的备份不能算作备份。恢复是人们经常跳过的步骤:现在就将一个 dump 解压到备用服务器上,这样可以在平稳时段熟悉流程,而不是等到服务中断时再操作。
按场景选择
只有一名用户、一台笔记本电脑和一台 VPS,且不需要浏览代码:使用基于 SSH 的裸仓库。不需要运行额外服务,也没有需要升级的组件。
条件相同,但还需要在浏览器中查看代码并分享代码链接:添加 cgit。仍然不需要数据库,也不需要常驻服务。
需要互相审查代码并跟踪问题的团队:使用 Forgejo 或 Gitea,服务器内存至少为 2 GB。等 CI 任务量增加后,再将 CI runner 移到第二台服务器。
需要容器注册表和审计记录,且服务器可提供 16 GB 内存的组织:使用 GitLab。低于此内存预算时,不要部署 GitLab。
升级前 3 个层级的成本较低,因为这些层级中的仓库都是磁盘上的普通 Git 目录。应从能够满足需求的最低层级开始。如果你正在评估同一台服务器上还有哪些服务值得占用空间,值得自行托管的服务简表会将 Git 服务器与其他争用这些内存的服务放在一起比较。
FAQ
1 GB VPS 可以运行 Forgejo 或 Gitea 吗?
可以。对于小型团队,使用 SQLite,且服务器上没有其他高负载服务时即可。Gitea 文档指出,1 GB 内存和 2 个 CPU 核心通常足以支持小型团队和项目;Forgejo 是 Gitea 的分支,资源需求大致相同。不要在这台机器上再部署 PostgreSQL 或 CI runner。如果服务在自身日志中没有报错却消失,请运行 sudo dmesg -T | grep -Ei 'oom|out of memory|killed process':如果输出中有一行列出被终止的进程,说明是内核的 OOM killer 终止了该进程。此时应升级服务器配置,而不是调整某个参数。
Forgejo 和 Gitea 有什么区别?
两者拥有共同的代码历史,并支持大多数相同的功能。Gitea 于 2016 年从 Gogs 分支而来;2022 年底,Gitea 商标的控制权转移给一家企业后,Forgejo 又从 Gitea 分支而来。Forgejo 由德国非营利组织 Codeberg e.V. 发布,采用 GPLv3 许可证;Gitea 继续采用 MIT 许可证,并获得商业支持。实际差异在于迁移路径。2025 年 1 月发布的 Forgejo v10.0 是最后一个可以直接使用 Gitea 数据库的版本,而且仅支持 Gitea v1.22 或更早版本。因此,当前的 Gitea 实例没有受支持的原地切换方式。
可以在自托管 Git 服务器上运行 GitHub Actions 工作流吗?
Forgejo Actions 和 Gitea Actions 都可以运行使用 GitHub Actions YAML 语法编写的工作流,工作流分别从 .forgejo/workflows 和 .gitea/workflows 读取。您需要安装单独的 runner 程序,并将其注册到自己的实例。许多已发布的 action 无需修改即可运行,但调用 GitHub API 的 action 无法运行。runner 会执行代码仓库中的任意代码,并且在使用常见的容器标签时,会为每个任务启动一个容器。因此,应为它单独准备一台主机,或至少使用单独的非特权用户;不要将它部署在已经运行代码托管服务的 1 GB 服务器上。
如何备份自托管 Git 服务器?
对于裸仓库,可以从另一台机器运行 git clone --mirror,复制所有 ref 和对象;在该镜像中运行 git remote update,即可刷新镜像。对于 Forgejo 或 Gitea,仓库只是状态的一部分,因为 issue、pull request、用户和密钥都存储在数据库中。请使用内置转储功能 sudo -u git forgejo dump -c /etc/forgejo/app.ini;如果使用 Docker 部署,也可以在容器内运行同一命令。将归档文件复制到服务器之外,并在备用机器上完整恢复一次,以确认恢复流程可用。