Skip to content

自建 Gitea:半小时能用的最小组合 ​

需求很朴素:一个自己能控制的 Git 服务,有网页界面、能 HTTPS clone、私有仓库、不依赖任何第三方。

Gitea 是这个需求下最省事的选择:单个二进制 / 单容器、SQLite 就能跑、资源占用低、界面完整。这篇是我在 NAS 上把它跑起来并接到公网的实际步骤和踩过的坑。

架构(最小可用) ​

浏览器 ──https──> Cloudflare 隧道 ──http──> 127.0.0.1:3000  (Gitea Web)
git@    ──ssh───> 宿主 2222 端口 ────────> 容器 22  (Git SSH)
                       │
                  /vol1/1000/docker/gitea/data  (仓库 + SQLite + 配置)

三个关键决定:

  • SQLite:家用场景没有并发压力,少一个数据库容器就少一个要备份的东西
  • Web 端口 3000:走隧道出去,不需要开防火墙
  • SSH 用 2222:宿主的 22 被系统占了,映射到宿主 2222

一、拉镜像(国内网络的必要绕路) ​

bash
docker pull docker.m.daocloud.io/gitea/gitea:1.27.3
docker tag  docker.m.daocloud.io/gitea/gitea:1.27.3 gitea/gitea:1.27.3

镜像源的对比见 拉不到镜像怎么办。要点:tag 回标准名,这样 compose 文件里写的是标准镜像名,以后换个源不用改文件。

二、用环境变量跳过安装向导 ​

Gitea 第一次启动会进网页安装向导。别走那个向导 —— 它生成的 app.ini 藏着一堆默认值,容器重建时容易丢。全用环境变量:

yaml
services:
  gitea:
    image: gitea/gitea:1.27.3
    container_name: gitea
    restart: unless-stopped
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=sqlite3
      - GITEA__server__DOMAIN=git.example.com
      - GITEA__server__ROOT_URL=https://git.example.com/
      - GITEA__server__HTTP_PORT=3000
      - GITEA__server__SSH_PORT=2222          # 显示给用户 clone 用的端口
      - GITEA__server__SSH_LISTEN_PORT=22     # 容器内监听端口
      - GITEA__service__DISABLE_REGISTRATION=true
      - GITEA__security__INSTALL_LOCK=true    # 跳过安装向导
    volumes:
      - ./data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "127.0.0.1:3000:3000"
      - "2222:22"

⚠️ INSTALL_LOCK=true 是跳过向导的关键。不设它,第一次访问会被 302 到 /install。

⚠️ Web 端口绑 127.0.0.1,不要 0.0.0.0。外网访问统一走隧道,别多开一个入口。

三、坑一:SSH 端口冲突 → 容器无限重启 ​

我一开始同时设了:

START_SSH_SERVER=true     # 让 Gitea 自己起内置 SSH 服务

结果容器无限重启。原因:镜像里自带了一个 OpenSSH 监听 22,再加内置 SSH 服务也去抢 22 → 端口被占 → 启动失败 → 重启循环。

去掉 START_SSH_SERVER 就好。默认行为(用镜像里的 OpenSSH + Gitea 注入 authorized_keys)就是你要的。

判断方法:容器一直 restarting 时,先看日志最后几行,通常就是 bind: address already in use 这种。

bash
docker logs --tail 30 gitea

四、坑二:接隧道时 service 必须带 scheme ​

注册隧道域名时:

bash
# ❌ Cloudflare 报 1056(origin 配置无效)
{"domain":"git.example.com","service":"localhost:3000"}

# ✅
{"domain":"git.example.com","service":"http://localhost:3000"}

这个坑的细节(包括凭据内存缓存、一个 appName 只能一个域名)在 闸道那篇 里。

五、验证:别只测首页 ​

部署完"首页 200"什么都证明不了。Git 服务最少验证这四条:

bash
# 1) 外网 HTTPS 首页
curl -s -o /dev/null -w '%{http_code}\n' https://git.example.com/

# 2) HTTPS clone(公开仓库,或带 token)
git clone https://git.example.com/<user>/<repo>.git /tmp/t1

# 3) SSH 认证(这条最容易挂)
ssh -T git@git.example.com -p 2222
# 期望输出类似:Hi <user>! You've successfully authenticated...

# 4) SSH clone
GIT_SSH_COMMAND='ssh -p 2222' git clone git@git.example.com:<user>/<repo>.git /tmp/t2

第 3 步是最有诊断价值的:它同时验证了端口映射、authorized_keys 注入、容器内 sshd 状态。首页 200 但 ssh -T 失败,说明只有 Web 那半边是通的。

⚠️ 测试完记得清理:测试仓库、临时密钥、/tmp/t1 /tmp/t2。

六、管理员和注册 ​

  • 第一个注册的用户就是管理员。所以要么先注册自己,要么直接关掉注册
  • 生产环境建议:关闭公开注册(DISABLE_REGISTRATION=true),需要的人由管理员在后台建

七、备份(Gitea 有官方工具) ​

仓库数据在 /data 里,但直接拷 /data 不是最好的做法,因为 SQLite 可能正在写。用官方 dump:

bash
docker exec -u git gitea gitea dump -c /data/gitea/conf/app.ini -f /data/backup/gitea-dump.zip

出来的 zip 包含仓库、数据库、配置、密钥。异地备份时记得加密 —— 里面有 SSH 私钥和用户密码哈希。

加密 + 云挂载的完整姿势见 云挂载为什么不能直写(注意:不能直接 > 写到云挂载点)。

八、升级注意 ​

Gitea 的版本升级有时候带数据库迁移。所以:

  1. 升级前先跑一次 gitea dump
  2. 镜像 pin 到具体版本号(别用 :latest),我在 compose 里写死 1.27.3 就是为此
  3. 升级后第一次启动会跑迁移,日志里能看到,别急着 Ctrl+C

检查清单 ​

  1. 镜像走加速源拉,tag 回标准名
  2. 环境变量配全 + INSTALL_LOCK=true 跳过向导
  3. Web 端口只绑 127.0.0.1,外网走隧道
  4. 不要设 START_SSH_SERVER(会撞端口)
  5. 隧道 service 带 http:// 前缀
  6. 四条验证:首页 / HTTPS clone / ssh -T / SSH clone
  7. 关掉公开注册
  8. 用 gitea dump 备份,异地包要加密

一句话版本:Gitea 最小可用 = SQLite + 环境变量 + INSTALL_LOCK + 隧道;两个必踩的坑是 SSH 端口冲突和隧道 service 缺 scheme。