Skip to content
twinkling.top服务GiteaEN
板型any Pi 4 or 5; repositories are text and the SQLite database is small
内核6.6.51+rpt-rpi-v8
空闲温度48 °C
内核架构arm64
参考机:Pi 4B 8GB / Bookworm 64-bit

部署配方 · 开发与 Git

机架上的 Gitea:不需要订阅的 git

这家里其它一切都依赖某个地方的仓库:Caddyfile、compose 文件、仪表盘 JSON、以及解释「为什么缓冲池是 512MB」的那些笔记。把它们放在免费的托管服务上可以用,直到那家服务改了条款。树莓派上的 Gitea 占用 320MB 内存和一个 SQLite 文件,它让这台机架自己描述自己。

gitea/gitea:1.23.4-rootless · 2026-09-16

Gitea rack plate: board stack and host ports
服务参数
容器镜像gitea/gitea:1.23.4-rootless
宿主端口:3002/tcp, :2222/tcp
数据目录/srv/homelab/gitea/data
内存预留320 MB
CPU 上限0.50 vCPU (cpus: "0.50")
更新节奏quarterly, and always read the release notes: Gitea ships database migrations that run on first boot and there is no downgrade
ARM 兼容arm64 native, and the rootless tag avoids running git with capabilities it does not need
端口映射与暴露面
宿主容器proto暴露用途
:30023000tcp仅局域网web UI, HTTP git remote and the API
:22222222tcp仅局域网SSH remote for git, which is what makes push work from a laptop

部署步骤

  1. 按 rootless 镜像的 uid 建好两个目录

    rootless 镜像以 uid 1000 运行,没有权限自己创建配置目录。两个数据卷都要在首次启动前改好属主。

    run
    sudo mkdir -p /srv/homelab/gitea/{data,config}
    sudo chown -R 1000:1000 /srv/homelab/gitea
  2. 首次启动前生成密钥

    缺少 SECRET_KEY 时 Gitea 会在首次启动时自己写一个,而这个密钥用于签名会话。从环境变量提供它,重建之后所有人不会被登出。

    run
    echo "GITEA__security__SECRET_KEY=$(openssl rand -base64 32)" >> /srv/homelab/.env
    echo "GITEA__security__INTERNAL_TOKEN=$(openssl rand -base64 48)" >> /srv/homelab/.env
  3. 启动后立刻创建管理员账号

    第一个注册的账号会成为管理员。在关闭注册之前把它注册好,并且走局域网而不是走代理。

    run
    cd /srv/homelab/gitea && docker compose up -d
    docker logs --tail 30 gitea
    # 打开 http://192.168.10.20:3002 注册管理员
  4. 关闭注册,并配好 SSH 客户端条目

    环境变量已经关掉了注册,但要在界面上再确认一次——首次启动写出的配置文件可能带着过期的值。

    run
    docker exec gitea gitea admin user list
    # 预期:只有你的管理员账号,且登录页没有注册入口
  5. 推一个仓库,证明 SSH 远端能用

    先用一个临时仓库测试,再把真实配置迁进来。HTTPS 能用而 SSH 不能用的远端,问题在密钥或端口上,而晚发现这件事更麻烦。

    run
    cd /tmp && git clone [email protected]:ops/sandbox.git && cd sandbox
    git commit --allow-empty -m 'first commit over ssh' && git push
    # 推送必须成功且不询问密码

这个体量下 SQLite 就是对的数据库

Gitea 的文档建议生产环境用 MySQL 或 Postgres,对团队来说这是对的。对一个家庭、二十个仓库、没有并发推送的场景,SQLite 意味着少一个容器、少一个密码,以及一个「复制文件」级别的备份。

它开始不够用的时候是并发写入:两个人同时在同一个大仓库上推送,偶尔会看到锁错误。真到那一步,迁到 Postgres 是有文档的迁移,而不是重搭。

SSH 端口的这个选择是刻意的

Gitea 的 SSH 远端监听 2222 而不是 22,理由和宿主机自己的 SSH 服务无关:它让「在路由器上转发一个端口、结果意外暴露了仓库服务」变得不可能,因为这个端口号不是任何人会去转发的那一个。

客户端这边就是 ~/.ssh/config 里一行,之后 git clone 和用任何其它远端一样。

shell
# ~/.ssh/config
Host git.lan
  HostName 192.168.10.20
  Port 2222
  User git
  IdentityFile ~/.ssh/id_ed25519

# 之后:git clone [email protected]:ops/rack-config.git

仓库里到底放什么

四个仓库,每一个的存在都是因为某个文件需要一份历史:rack-config(compose 文件和 Caddyfile)、rack-notes(运行手册:什么会坏、试过什么、为什么缓冲池是 512MB)、rack-backup(脚本与恢复演练)、rack-dashboards(Grafana JSON)。

最容易被跳过、却最重要的那个是备份仓库:一个从没被人复核过的备份脚本,就是一个四个月前就悄悄停止工作的备份脚本。这件事不需要 GitHub Actions——这个规模下,每周在 Grafana 里看一眼任务输出就够了。

Actions、Webhook,以及「在派上跑 CI」的诱惑

Gitea Actions 能用,它跑一个 runner 容器,可以在同一块板子上执行你的 CI。这很诱人,在这里却基本是个错误:一次把树莓派 CPU 占满四分钟的构建,会让机架上所有其它服务一起变慢,而这个家里维护的仓库都是配置文件,本来就不需要编译。

唯一值得做的自动化,是仓库收到推送到 main 时往通知渠道发一条 webhook。五行配置,它能兜住「半夜改了东西然后忘了」这种情况。

shell
# 仓库 -> 设置 -> Webhook -> 新增
# 推送事件、仅 main 分支,POST 到通知端点

compose 文件

整份文件放在 /srv/homelab/gitea/compose.yaml。标签固定版本号,不用 latest——树莓派上回滚比升级麻烦得多。

compose.yaml
services:
  gitea:
    image: gitea/gitea:1.23.4-rootless
    container_name: gitea
    restart: unless-stopped
    ports:
      - "3002:3000/tcp"
      - "2222:2222/tcp"
    environment:
      TZ: Asia/Shanghai
      GITEA__database__DB_TYPE: sqlite3
      GITEA__server__DOMAIN: git.lan
      GITEA__server__ROOT_URL: https://git.lan/
      GITEA__server__SSH_PORT: "2222"
      GITEA__service__DISABLE_REGISTRATION: "true"
      GITEA__service__REQUIRE_SIGNIN_VIEW: "true"
      GITEA__repository__DEFAULT_BRANCH: main
    volumes:
      - /srv/homelab/gitea/data:/var/lib/gitea
      - /srv/homelab/gitea/config:/etc/gitea
    networks: [rack]

networks:
  rack:
    external: true

加固清单

  • 关闭注册,账号只能由管理员创建
  • git 的 SSH 端口是 2222 而不是 22,这样被封锁或被误转发的 22 端口永远不会意外打到仓库服务上
  • 管理员账号强制开启双因素;在用的 API 令牌除备份任务那一个外全部只读
  • 启用了 LFS 但对象存储保持在本地;大二进制文件在贡献说明里被劝阻,而不是悄悄把固态盘填满

备份方案

gitea dump 会生成一个包含数据库、仓库、配置和附件的完整归档。每周跑一次,每次升级前再跑一次,归档拷到 NAS。仓库在每台推送过的笔记本上本来就有一份,除了 issue 之外这也算一份真实的第二副本。

上线后验证

  • 从笔记本 git push 走 SSH 成功,且不提示输入密码
  • gitea dump 生成的归档里同时包含数据库和仓库
  • 管理员账号已开启双因素,登录页上没有注册链接
  • 渲染一个带相对图片链接的 Markdown 文件正常,说明 raw 路径和 ROOT_URL 是一致的

踩过的坑

  • GITEA__server__ROOT_URL 必须是客户端真正使用的地址,含协议。在代理后面填错,克隆地址、头像和 webhook 内容里全是容器主机名。
  • rootless 镜像绑不了 22 端口,这也是 SSH 远端放在 2222 的另一个理由。把 SSH_PORT 设成 22 会得到一个能启动但拒绝所有连接的服务。
  • 升级会在启动时跑数据库迁移,而且没有降级路径。每次升版本之前先 dump 一次,十五秒的事,却决定了一个下午是难受还是只需恢复。
  • 加到 Gitea 里的 SSH 公钥和加到宿主机上的不是一回事。以 [email protected] 推送用的是容器的密钥库;把公钥塞进 ~/.ssh/authorized_keys 只会安静地什么都不发生。

硬件与选型问答

为什么不用免费的托管 git 服务?
公开代码就用它。这里改变的是:这些仓库就是机架的配置,而放在别人账号体系后面的配置,在对方账号出问题时是恢复不了的。存放配置的那台派,和配置所描述的备份,不是同一台机器——这就是重点。
自建 git 服务需要多少磁盘?
应用本身约 40MB,二十个小仓库含历史约 250MB,再加上你允许的 LFS 对象。在固态盘上这是舍入误差;在 SD 卡上,它是把整个 /srv 目录挪到固态盘的又一个理由。
能让 Gitea 给机架自己的配置跑 CI 吗?
可以,而且值得为唯一一个任务这么做:在配置被应用之前校验 Caddyfile 和 compose 文件。这个任务两秒就跑完,也不需要把 runner 钉在派上——caddy validate 和 docker compose config 可以在收到 webhook 的同一秒内跑完。