使用Gitea实现代码自动化部署
前言
服务器闲着也是闲着,本着“生命在于运行”的原则,决定给它再找点活干。之前项目一直用 GitHub Actions + 服务器 WebHook 部署,用久了有点腻,正好折腾一下私有流水线。反正最后容器也是在这台机器上跑,干脆连镜像也在这台机器上 build 算了。
所以这次就用了 Gitea + Act Runner。代码 push 到 Gitea,Runner 接到任务后在服务器上拉代码,接着执行 podman build 和 podman run。镜像 build 完就留在本机,省了推到镜像仓库再拉回来的步骤。

先把 Gitea 跑起来
Gitea 我用的是 1.26 的 rootless 镜像。我比较喜欢用 systemctl --user 管服务,所以这次也用了 Podman 的 Quadlet。只需要写一个 gitea.container,Podman 就会生成对应的 gitea.service,以后启动、重启都用 systemctl --user 就行。
下面是我用的 gitea.container(已脱敏)。数据库直接在 Gitea 的初始化页面填,我这里接的是已有的 MariaDB。
1 | [Unit] |
PublishPort 把 Gitea 的 3000 端口映射到 127.0.0.1:3000,方便宿主机上的 Act Runner(下面会提到) 连进来。ROOT_URL 填 Nginx 代理的域名地址(如果有),不然从网页上复制出来的 clone 地址可能就不对。
先把数据目录建好,再把 gitea.container 文件放到 ~/.config/containers/systemd/。
1 | mkdir -p ~/.config/containers/systemd ~/workspace/gitea/{data,config} |
改完配置之后用 systemctl --user restart gitea.service,起不来就用 journalctl --user -u gitea.service -e 看看,启动成功应该是这样的:

让 Act Runner 跑在宿主机上
光有 Gitea 还不行,它只负责安排任务,真正执行命令的是 Act Runner。
一开始我想着 Gitea 都放容器里了,Runner 也一起放进去得了。但是发现 Runner 和 rootless Podman 适配非常不好(官方教程都是基于 Docker 的),Podman 默认的 socket 在 $XDG_RUNTIME_DIR/podman/podman.sock,跟 Docker 常用的 /var/run/docker.sock 不在一个地方,并且权限什么的也不一样。从 Runner 容器里调用宿主机的 Podman,除了挂载路径,还得看容器里的用户有没有权限访问这个 socket。我当时 podman build 跑得好好的,一到 podman run,要么报权限不够,要么报找不到 Docker daemon。
折磨了我很久的报错:
1 | failed to create container: 'Error response from daemon: make cli opts(): making volume mountpoint for volume /var/run/docker.sock: mkdir /var/run/docker.sock: permission denied' |
折腾了一圈,干脆把 Runner 放回宿主机,用平时跑 Podman 的那个普通用户启动。这样 workflow 里执行的命令,跟我 SSH 上去手敲的是一回事。
Act Runner 的二进制我放在 ~/workspace/gitea/runner。宿主机上除了 Git 和 Podman,还得装 Node.js,actions/checkout@v4 要用它。这里有个容易搞混的地方:项目代码从自己的 Gitea 拉,checkout 这个 action 本身默认还是从 GitHub 下载的。
首先生成 Runner 运行配置:
1 | ./act_runner generate-config > config.yaml |
生成的 config.yaml 挺长,这次主要看这几项:
1 | runner: |
如果 Node.js 是用 nvm 装的,先在终端里执行 command -v node,然后把实际的 bin 目录填进 PATH。平时登录进去能运行 node,不代表 systemd 启的 Runner 也能找到它。
host:host 前一个 host 是标签名,对应 workflow 里的 runs-on: host;后一个 host 是执行方式,表示直接在宿主机上跑。启动 Runner 的用户要跟平时手动执行 Podman 的用户是同一个,因为 rootless Podman 的镜像和容器是按用户存的。
在仓库的设置里拿到注册 token:

然后在 Runner 目录里执行注册命令:
1 | ./act_runner --config config.yaml register |
按提示输入 Runner 能访问的 Gitea 地址、注册 token 和名称。注册之后会生成 .runner 文件,不用管他。
接下来跟之前管其他后台服务差不多,写一个 ~/.config/systemd/user/act_runner.service:
1 | [Unit] |
1 | systemctl --user daemon-reload |
WorkingDirectory 别漏了,上面的 .runner 是相对路径。如果希望服务器重启之后不登录也能启动这两个用户服务,可以给部署用户开启 linger:sudo loginctl enable-linger "$(id -un)"。
编写部署 workflow
这部分和以前写 GitHub Actions 差不多。项目根目录放 Containerfile,workflow 放在 .gitea/workflows/deploy.yml。平时 push 到 main 自动部署,想手动跑一遍就去 Actions 页面点。
我常用的 Workflow 大概长这样:
1 | name: Build and deploy |
这个部署脚本其实就干这么几件事:先用 podman build 打一个带 :build 标签的镜像,再跟现在的 :latest 比镜像 ID。如果一样,摘掉临时标签就结束,容器接着跑。如果不一样,就把原来的 :latest 留作 :rollback,让新镜像接手 :latest,再用它重新起容器。
:rollback 留着是为了真出问题时能手动退回上一版,虽然我几乎从来没用到过。
最后
折腾了这么一圈,总算达到了和 Github Actions 相似的效果,但并不是一无所获,起码我累到了 🤓
