SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor

自托管 Git 服务器怎么选:Forgejo、Gitea 还是 cgit

比较 SSH 裸仓库、cgit、Forgejo、Gitea 和 GitLab 的 RAM 需求,说明 1 GB VPS 能运行哪些方案,以及 GitLab 为何无法运行。

应运行哪种自托管 Git 服务器

自托管 Git 服务器并不是单一产品。VPS 的 RAM(随机存取存储器)决定了您可以运行哪种方案。Git 不需要专用守护进程:在您能租用的最小型服务器上,裸仓库加上一个 SSH(安全外壳)账户就已经是可用的服务器。再往上,您需要选择在其旁边运行的 Web 应用,而每提升一个级别,都会增加小型 VPS 可能无法提供的内存需求。

共有四个级别。通过 SSH 提供裸仓库,不新增任何监听服务。cgit 是快速的只读 Web 查看界面,不需要数据库。Forgejo 或 Gitea 是完整的代码托管平台,提供账户、议题和拉取请求,只需几百 MB 内存。GitLab 则要求服务器的配置比其他方案高出许多倍。

根据您需要完成的工作做出选择,然后将内存需求与您付费使用的套餐进行对照。

每个选项实际需要多少 RAM

这些项目中只有两个公开了硬件配置。将公开配置视为最低要求,而不是性能保证。实例运行后,使用 systemd-cgtopps -o rss= -C forgejo 测量实际占用。

ChartRAM the projects document, official docs, August 2026
The data behind this chart
[
  {
    "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 的配置是你能找到的最接近的公开参考。

这对 1 GB VPS 意味着:裸仓库和 cgit 可以运行,并且仍有剩余内存,因为它们都不运行常驻服务。Forgejo 或 Gitea 可以启动,并通过 SQLite 为小型团队提供服务,但这已经处于文档所列的最低配置,因此不要在这台主机上运行 PostgreSQL 和 CI(持续集成)runner。如果 Web 界面无错误地消失,请运行 sudo dmesg -T | grep -i oom,并查找类似 Out of memory: Killed process 1181 (forgejo) 的行。这表示内核的 out of memory killer 终止了该进程。在 1 GB 主机上运行 GitLab 不是调优问题,而是无法运行。

Tier 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 创建一个没有工作副本的仓库,这正是服务器端保存的仓库类型。向包含工作副本的仓库推送时,Git 会因 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_keys
git 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)" git

git-shell 只接受 Git 通过 SSH 发送的少数命令,因此交互式登录现在会显示一条消息并退出,而不是提供提示符:

fatal: Interactive git shell is not enabled.
hint: ~/git-shell-commands should exist and have read and execute access.

这就是完整的服务器。不需要数据库,也不需要升级 Web 进程。代价是失去代码托管平台提供的所有功能:无法浏览代码、没有问题跟踪器、没有拉取请求,也没有按用户设置的权限。该文件中的每个密钥都可以读写 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/git

scan-path 会遍历该目录,并列出找到的每个仓库,因此新建的裸仓库无需额外配置即可显示。cache-size 表示缓存页面的数量;该值为零时,缓存保持关闭。添加配置行之前,先查看软件包已经写入 /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/description
nginx 站点文件及检查方法
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 Listen

root /usr/share/cgitcgit.csscgit.png 作为普通文件提供,try_files 则将其他请求交给 /usr/lib/cgit/cgit.cgi 中的 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 基本身份验证。CGI 以 Web 服务器用户身份运行,因此该用户必须能够遍历 /srv/git 并读取每个仓库。若该用户无法进入某个目录,该目录会在索引中显示为空,而不是报告错误。

第 2 层:使用 Forgejo 或 Gitea 管理 issue 和 pull request

Forgejo 和 Gitea 的理念相同:使用一个 Go 二进制文件提供 Web 代码托管平台,包含用户、组织、issue、pull request、release、软件包注册表和内置 CI 系统。安装只需要二进制文件和 SQLite,因此可以部署在 GitLab 无法运行的硬件上。下面的 Compose 文件来自 Forgejo 文档,镜像标签为截至 August 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 -1

curl 行应输出 HTTP 状态行。在完成首次设置前,它可能会重定向到 /install,这仍然表示服务已启动。如果容器直接退出,通常原因是所有权配置错误:./forgejo 目录必须归 USER_UID 中的 UID(用户 ID)所有,否则进程无法写入自己的数据目录。VPS 上的 Docker Compose 详细介绍了该文件布局和卷所有权规则。

设置页面上的两个选项决定 clone URL 是否可用。SSH 端口必须设置为 222,因为 Compose 文件将主机端口 222 映射到容器的 22 端口;域名必须设置为用户实际输入的名称。任一项填写错误,所有仓库页面都会提供对复制该命令的用户均不可用的 clone 命令。之后,这两项都位于 app.ini[server] 部分中,分别显示为 SSH_PORTSSH_DOMAINROOT_URL

对于公网实例,只将 Web 端口发布到 loopback 地址('127.0.0.1:3000:3000'),并在前面使用 nginx 处理 TLS(传输层安全)。Gitea 也可使用 gitea/gitea 镜像以相同方式安装,或者使用单个二进制文件、一个 systemd unit 和一个 app.ini 安装。截至 August 2026,其当前稳定版本为 1.27.1。

尽可能使用 SQLite。这样实例只需一个进程和一个文件,并且重启后无需监管额外服务即可恢复运行。当多人同时写入时,PostgreSQL 才值得增加维护成本,因为 SQLite 会串行处理写入,而较长的 CI 运行会持续写入。两个项目都支持以后将现有实例迁移到 PostgreSQL,因此这不是无法更改的决定。

Forgejo 和 Gitea:实际有哪些差异

二者源自同一项目。Gitea 于 2016 年从 Gogs 分叉而来。2022 年末,Gitea 的域名和商标控制权转交给 Gitea Ltd。随后,几名维护者与 Codeberg 共同启动了 Forgejo。Forgejo 由 Codeberg e.V. 发布。Codeberg e.V. 是一家在德国注册的非营利协会。Forgejo 于 2024 年从 MIT 许可证改用 GPLv3(GNU 通用公共许可证第 3 版)。Gitea 继续使用 MIT 许可证,并在商业支持下开发。

日常使用中,两者的功能集相近。但二者之间的迁移路径并不相同。2025 年 1 月发布的 Forgejo v10.0,是最后一个可以直接使用 Gitea 数据库的版本,而且仅支持 Gitea v1.22 或更早版本。截至 2026 年 8 月,Gitea 的版本为 1.27.1。因此,当前的 Gitea 实例不支持原地切换到 Forgejo。请在写入数据前选定其中一个项目,并将之后的迁移视为导出后重新导入。

选择时可以遵循一条简单原则。如果您重视治理方式,或希望项目由非营利组织维护,请运行 Forgejo。如果您希望使用更广泛的安装基础和商业支持选项,请运行 Gitea。两者都采用开放方式维护,并且经常发布版本:Forgejo 每三个月发布一个稳定版本,每年发布一个 LTS(长期支持)版本。截至 2026 年 8 月,当前版本为 v16.0.2,LTS 版本为 v15.0.6。

第 3 层:GitLab 在执行任何操作前的资源成本

GitLab CE 属于另一类软件。一个实例由多个协同工作的服务组成:用于 Web 应用的 Puma、用于后台作业的 Sidekiq、PostgreSQL、Redis、用于访问仓库的 Gitaly,以及前置的 nginx。Omnibus 软件包会将这些组件一起安装,因此安装过程简单,但内存最低需求很高。

GitLab 的需求页面说明,单节点安装的基线配置为 16 GB RAM 和 8 vCPU;在内存受限的环境中,8 GB 被列为最低配置。同一页面还要求禁用 swap,因为高负载下发生交换会严重降低实例性能。这些是截至 August 2026 发布的数值,而且多年来一直在上升,因此在规划服务器配置前,请重新查看该页面。

相应的资源投入可以获得实际功能:容器注册表、软件包注册表、细粒度权限、合规与审计功能,以及经过大规模验证的 CI。如果团队中没有人能说出本季度需要上述功能中的任何一项,那么您支付的只是更大规格的 VPS,却没有获得实际收益。

SSH 访问模型:一个 Git 用户和多个密钥

这里的每个层级都使用相同的身份验证方式。系统中只有一个名为 git 的 Unix 帐户,所有公钥都写入该帐户的 ~/.ssh/authorized_keys。身份验证依据是密钥。授权范围由同一行中写在密钥前面的选项决定。

普通密钥行会授予持有者该帐户能够执行的所有操作。强制命令可将权限限制为 Git:

restrict,command="git-shell -c \"$SSH_ORIGINAL_COMMAND\"" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIexamplekeyhere alice@laptop

restrict 从 OpenSSH 7.2 开始可用,只需一个选项即可关闭端口转发、代理转发、X11 转发和 PTY(伪终端)分配。command= 会用你指定的命令替换客户端请求的命令。Git 仍可正常工作,因为 Git 会在 $SSH_ORIGINAL_COMMAND 中发送请求。

代码托管平台会代你写入该文件。这正是第 0 层和第 2 层之间的实际区别。Forgejo 和 Gitea 会重写 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 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIexamplekeyhere alice

强制命令使一个共享的 Unix 帐户能够实现按用户区分的权限:key-3 会告诉代码托管平台当前连接的是哪个用户,平台会在传输任何对象前检查该用户是否有权访问该仓库。不要在由代码托管平台管理的服务器上手动编辑该文件,因为平台会根据数据库重新生成文件,你添加的行会消失。部署密钥使用相同的机制:部署密钥是注册到单个仓库的普通 SSH 密钥,通常为只读权限;权限检查由代码托管平台完成,而不是由 sshd 完成。

有两项操作习惯比上述配置更重要。每个人或每台机器使用一个独立密钥,绝不要共享密钥,因为撤销共享密钥意味着必须同时为所有人轮换密钥。用户离开当天就删除其密钥,因为文件中遗留的旧密钥会永久保留登录权限,而且没人会监控它。服务器上的良好 SSH 密钥管理介绍了密钥类型和密码短语,这些内容在此完全适用。如果服务器是新的,应先完成新 VPS 上的前十分钟,再将仓库放到服务器上。

我可以在自己的 Git 服务器上运行 GitHub Actions 吗?

您可以运行使用 GitHub Actions 语法编写的工作流,但不能运行 GitHub。Forgejo Actions 从 Forgejo v1.21 开始默认启用,并会读取每个仓库中 .forgejo/workflows 的工作流文件。Gitea Actions 的工作方式相同,并会读取 .gitea/workflows。两者都需要额外安装 runner,并使用管理员设置中的令牌将其注册到您的实例。许多已发布的 action 无需修改即可运行;但凡调用 GitHub API 或依赖 GitHub 托管基础设施的 action 都无法运行。

请考虑两个后果。runner 会为每个作业启动一个容器,因此需要容器引擎和独立的内存预算。这也是 runner 不应与代码托管服务部署在同一台 1 GB 服务器上的原因。runner 会执行工作流文件中的所有指令,而 Forgejo 文档对此有明确说明:runner 会执行远程代码。条件允许时,请为它使用独立主机;至少也应使用独立的非特权用户,并使用限定到单个仓库的注册令牌。

如果您的仓库仍保留在 GitHub 上,而您只希望使用自己控制的硬件来执行计算,这是另一种配置,步骤也不同:自托管的 GitHub Actions runner 会连接到 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 runner 移到第二台服务器。

需要容器注册表和审计记录的组织:使用 GitLab,并为服务器准备 16 GB 内存。低于这一配置要求时,不要部署 GitLab。

升级到前三个层级的成本较低,因为这些层级中的仓库都是磁盘上的普通 Git 目录。选择能够满足需求的最低层级。如果你正在规划同一台服务器上还应部署哪些服务,值得自行托管的服务清单会将 Git 服务器与其他争用内存的服务放在一起比较。

FAQ

1 GB VPS 可以运行 Forgejo 或 Gitea 吗?

可以。对于小型团队,在使用 SQLite 且服务器上没有其他高负载服务的情况下可以运行。Gitea 文档指出,1 GB RAM 和 2 个 CPU 核心通常足以支持小型团队和项目;Forgejo 是 Gitea 的分支,资源需求大致相同。不要在这台机器上再部署 PostgreSQL 或 CI runner。如果服务消失,但自身日志中没有错误,请运行 sudo dmesg -T | grep -i oom:如果输出中有一行列出被终止的进程,说明是内核的 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 会执行代码库中的任意代码,并为每个任务启动一个容器。因此,应为它单独准备一台主机,或者至少使用单独的非特权用户。不要将它部署在已经运行 forge 的 1 GB 服务器上。

如何备份自托管 Git 服务器?

对于裸仓库,在另一台机器上运行 git clone --mirror 可以复制所有 ref 和对象;在该镜像中运行 git remote update 可以刷新镜像。对于 Forgejo 或 Gitea,代码库只是状态的一部分,因为 issue、pull request、用户和密钥都存储在数据库中。请使用内置的 dump 功能 sudo -u git forgejo dump -c /etc/forgejo/app.ini;如果通过 Docker 安装,也可以在容器中运行同一命令。将归档文件复制到服务器之外,并在备用机器上完整恢复一次,以确认恢复流程可用。

#git#自托管#forgejo#gitea#SSH