A deep dive into my Forgejo setup
A deep dive into my Forgejo setup
深入探索我的 Forgejo 设置
Like many others recently I got fed up by what seems to be a steady and inexorable degradation of GitHub quality of service (more on that later) and decided to walk down the self host path for all my things Git, Docker, PRs and CI/CD. In this article I’m going to show you with some level of detail how my new self hosted Forgejo setup currently looks like and how well it fits me as a near total replacement for GitHub (spoilers ahead: very well). I’ll provide links to actual repositories plus snippets of reusable configuration in case you might want to try it for yourself.
最近,像许多人一样,我对 GitHub 服务质量似乎正在持续且不可避免地下降感到厌倦(稍后会详细说明),因此决定走上自托管之路,将我所有的 Git、Docker、PR 和 CI/CD 需求都迁移到自己的服务器上。在本文中,我将详细展示我新搭建的自托管 Forgejo 环境,以及它作为 GitHub 的近乎完美替代品表现如何(剧透一下:非常好)。我将提供实际仓库的链接以及可复用的配置片段,以供你参考尝试。
Why Forgejo
为什么选择 Forgejo
To be honest I didn’t spend much time selecting which OpenSource git forge to adopt. I heard well about other options like SourceHut for example BUT I already had a Codeberg (the company behind Forgejo development) account and I like their mission; it exposes most of GitHub APIs verbatim so things like GitHub actions should work out of the box; it’s feature rich, considered stable and follows a regular release cycle; the UI is almost identical to GitHub’s; it’s written in Go, I like Go. Not exhaustive and much for an heuristic, I suppose, but has been enough for me.
老实说,我并没有花太多时间去挑选该采用哪种开源 Git 托管平台。我听说过其他不错的选择,比如 SourceHut,但我已经拥有一个 Codeberg(Forgejo 开发背后的公司)的账号,并且我很认同他们的使命;它几乎原样暴露了 GitHub 的 API,因此像 GitHub Actions 这样的功能应该可以直接使用;它功能丰富、被认为是稳定的,并遵循定期的发布周期;UI 与 GitHub 几乎一模一样;它是用 Go 编写的,而我喜欢 Go。这算不上详尽的评估,可能更多是基于直觉,但对我来说已经足够了。
How it looks like
现状如何
The forge is live at https://forge.l3x.in and is already home for a few of my OpenSource projects and all of my private repositories, with plans to migrate there most of the others I currently have on GitHub (more on that later). Everything so far works pretty well: the web UI feels familiar and snappy, migrating / mirroring repos is just a click away, and CI/CD latency and build times are fantastic ⚡️ Just a quick comparison from a single project involving Docker (Podman) build to prove the point: on GitHub: 20m 54s on my Forgejo Runner: 3m 19s. Almost 18 minutes saved for building (and pushing to registry) the very same application, not to mention from now on I won’t have to wait for my turn in the GH build queue. This win alone might have been worth the effort, I’ll expand on what works better than on GitHub and what not later on but let’s get into some technical details first.
该托管平台现已上线至 https://forge.l3x.in,它已经托管了我的一些开源项目和所有私有仓库,并计划将我目前在 GitHub 上的其他项目也迁移过来(稍后详述)。到目前为止,一切运行良好:Web UI 感觉既熟悉又灵敏,迁移/镜像仓库只需点击一下,CI/CD 的延迟和构建时间也非常出色 ⚡️ 仅以一个涉及 Docker (Podman) 构建的项目为例进行对比:在 GitHub 上需要 20 分 54 秒,而在我的 Forgejo Runner 上仅需 3 分 19 秒。构建(并推送到镜像仓库)同一个应用程序节省了近 18 分钟,更不用说从现在开始,我再也不用在 GitHub 的构建队列中排队等待了。仅此一项收益就足以证明这次折腾是值得的。稍后我会详细说明哪些方面比 GitHub 更好,哪些方面有所不足,但现在让我们先进入一些技术细节。
The Stack
技术栈
A comprehensive list of the components making up my forge: a small Debian Trixie amd64 VPS to host all the things; Systemd and Podman (Quadlets) to manage the services and recurring tasks; Caddy to terminate TLS requests and auto renew certificates; Forgejo and Forgejo runner for CI/CD; Webhook to restart services programmatically; Ansible and OpenTofu to configure all the things the GitOps way; Renovate to keep everything installed up to date; Restic to backup Podman volumes daily; Axiom (via Vector) to collect Journald logs and alert on errors.
构成我托管平台的组件清单如下:一台小型 Debian Trixie amd64 VPS 用于托管所有内容;Systemd 和 Podman (Quadlets) 用于管理服务和周期性任务;Caddy 用于处理 TLS 请求并自动续期证书;Forgejo 和 Forgejo Runner 用于 CI/CD;Webhook 用于以编程方式重启服务;Ansible 和 OpenTofu 以 GitOps 方式配置所有内容;Renovate 用于保持所有已安装软件的更新;Restic 用于每日备份 Podman 卷;Axiom(通过 Vector)用于收集 Journald 日志并在出现错误时发出警报。
Virtual Private Server
虚拟专用服务器 (VPS)
I pay an annual subscription (~60€) to OVH in exchange for a dedicated Linux amd64 VPS with 4 vCPUs cores, 8GB of ram, 75 GB of storage, plus one fixed IPv4 and one IPv6 address. It might not sound like much but you’d be surprised by how much something so low profile can still punch nowadays 🥊 Nothing fancy on the OS side either: I’m an old time Debian user and I already have a bunch of Ansible roles to setup on said VPS common services like SSHd, Postfix and so on; it comes natural to just drop another role for a new service whenever a new itch needs scratching. What changed now thanks to having my personal Forgejo is how I run config management / IaC tools like Ansible and OpenTofu. More on that later.
我每年支付约 60 欧元的订阅费给 OVH,换取一台专用的 Linux amd64 VPS,配置为 4 核 vCPU、8GB 内存、75GB 存储空间,以及一个固定的 IPv4 和一个 IPv6 地址。这听起来可能不算什么,但你会惊讶于如今这种低配置的设备依然能发挥多大的威力 🥊 操作系统方面也没什么花哨的:我是一名资深的 Debian 用户,我已经有一堆 Ansible 角色可以在上述 VPS 上设置 SSHd、Postfix 等常用服务;每当有新需求时,只需添加一个新的角色即可,这非常自然。现在,得益于拥有个人的 Forgejo,我运行 Ansible 和 OpenTofu 等配置管理/IaC 工具的方式发生了改变。稍后会详细说明。
Service Managers
服务管理器
For this project I decided to teach myself some Podman, with me having been using Docker for all of my containerize history so far. TL;DR: Docker Desktop is gone from my MacBook as I now use Podman exclusively for all the things containers both on my macOS workstation and Linux servers. Contrary as with Docker, with Podman there’s no need for any dedicated daemon process proxying commands and Quadlets work remarkably well in tandem with Systemd to run containerized services. I now run all Podman containers as rootless and once the service unit files are created I can for example start/stop/inspect services with the usual systemctl / journalctl commands you might be already familiar with. Once services are enabled (systemctl enable), Systemd will take care of keeping them up and running after a host reboot, application crash, accidental podman kill, etc.
对于这个项目,我决定自学一些 Podman,因为我之前的容器化历史一直都在使用 Docker。简而言之:我的 MacBook 上已经卸载了 Docker Desktop,因为我现在在 macOS 工作站和 Linux 服务器上都完全使用 Podman 来处理所有容器事务。与 Docker 不同,Podman 不需要任何专门的守护进程来代理命令,而且 Quadlets 与 Systemd 配合得非常好,可以运行容器化服务。我现在以 rootless(无根)模式运行所有 Podman 容器,一旦创建了服务单元文件,我就可以使用你可能已经熟悉的常规 systemctl / journalctl 命令来启动/停止/检查服务。一旦服务被启用(systemctl enable),Systemd 就会负责在主机重启、应用程序崩溃、意外的 podman kill 等情况后保持它们的运行。
Example: manage Forgejo process
示例:管理 Forgejo 进程
This is how the unit file for Forgejo looks like: 这是 Forgejo 的单元文件内容:
# ~/.config/containers/systemd/forgejo.container
[Container]
Image=codeberg.org/forgejo/forgejo:16
ContainerName=forgejo
Environment=USER_UID=1000
Environment=USER_GID=1000
Environment=FORGEJO____APP_NAME=Forgejo
[...]
Pull=newer
CgroupsMode=disabled
PublishPort=127.0.0.1:3000:3000
PublishPort=[::1]:3000:3000
PublishPort=2222:2222
Volume=forgejo-data:/data
[Service]
Restart=always
[Install]
WantedBy=default.target
The Pull=newer is there to ensure I’m always running an up-to-date Forgejo stable version (a daily timer backup script takes care of restarting the service, that’s when the image pull kicks in). This is how I can inspect service’ status:
设置 Pull=newer 是为了确保我始终运行的是最新的 Forgejo 稳定版本(每日定时备份脚本负责重启服务,此时会触发镜像拉取)。以下是我检查服务状态的方式:
$ systemctl --user status forgejo.service
● forgejo.service
Loaded: loaded (/home/podman/.config/containers/systemd/forgejo.container; generated)
Active: active (running) since Sat 2026-07-18 00:00:43 UTC; 5h 24min ago
Invocation: 2193250002a74867a80486adc9194134
Main PID: 987445 (conmon)
Tasks: 50 (limit: 9257)
Memory: 530.5M (peak: 1G)
CPU: 46min 14.351s
CGroup: /user.slice/user-997.slice/user@997.service/app.slice/forgejo.service
To tail process’ logs: 查看进程日志:
$ journalctl --user -u forgejo.service -f
Jul 18 05:44:23 ovh.l3x.in forgejo[987445]: 2026/07/18 05:44:23 ...eb/routing/logger.go:102:func1() [I] router: completed GET [...]
You might have noticed the --user flag for all the commands: that’s required to interact with Systemd services not executed by root.
你可能已经注意到所有命令都带有 --user 标志:这是与非 root 用户执行的 Systemd 服务进行交互所必需的。
Podman own requirements
Podman 自身的需求
Actually I was lying (not very accurate) when listing the tools in use… The stack isn’t complete without at least mentioning all the packages required by Podman to run properly, networking and all the rest. This is the… 实际上,我在列出所用工具时撒了谎(或者说不够准确)……如果不至少提及 Podman 正常运行所需的软件包、网络以及其他所有内容,这个技术栈就不完整。这就是……