Featured image of post 自托管 Git 平台镜像实战:Gitea / Gitblit / Gogs 部署与提交前代码规范检查

自托管 Git 平台镜像实战:Gitea / Gitblit / Gogs 部署与提交前代码规范检查

Gitea / Gitblit / Gogs 三款轻量自托管 Git 服务的 docker 部署、对比、p3c-pmd 提交前检查、服务端 pre-receive 钩子——把"自建 Git 平台 + 守住代码质量"做成一份可落地的清单。

不是所有公司都需要"重型"的 GitLab EE。10 人以下小团队单仓库 / 单分支偶尔需要自托管——这种场景下,GiteaGitblitGogs 三款轻量自托管 Git 服务更合适:镜像小、启动快、吃内存少(一个 Gitea 跑 200 MB 内存,GitLab 起步就是 4 GB)。

自托管 Git 平台只是入口——真正让团队"省心"的是把代码规范检查内置到提交链路里:客户端 pre-commit 钩子挡住本地漏改的代码,服务端 pre-receive 钩子挡住别人 PR 漏改的代码。

阅读对象:需要从零搭自托管 Git 服务的小团队 leader,或想给现有 Gitea 加代码规范检查的开发者
覆盖范围:Gitea / Gitblit / Gogs 三款服务的镜像部署、对比、客户端 p3c-pmd pre-commit 检查、服务端 pre-receive 钩子、黑白名单机制

一、三款 Git 平台对比

维度GiteaGitblitGogs
语言GoJavaGo
镜像大小~50 MB~150 MB~30 MB
内存占用200 MB~500 MB~200 MB~
启动时间秒级30 秒秒级
维护活跃度极高(社区驱动,月级发版)低(停更风险)中(社区维护)
镜像选择gitea/gitea 官方gitblit/gitblit 第三方gogs/gogs 官方
Web UI现代(仿 GitHub)复古(类似 Bitbucket)简洁(仿 GitHub)
权限模型完善(团队/组织/仓库级)中等基础
CI/CD 内置有(Gitea Actions)
包管理(Packages)
迁移工具一键从 GitHub/GitLab/Gogs 导入导入脚本一键从 GitHub 导入

When to use

  • 新项目 / 长期维护 → Gitea(社区活跃、UI 现代、CI 内置)
  • 老牌 Java 团队 / 不想装 Go 运行时 → Gitblit(纯 Java 部署)
  • 极致轻量 / 资源紧张 → Gogs(镜像最小、维护尚可)

二、Gitea 部署(推荐)

2.1 镜像选择

镜像用途
gitea/gitea:latest最新版(开发版,不推荐生产
gitea/gitea:1.191.19 系列(推荐 LTS)
gitea/gitea:1.23-jdk81.23 + JDK8(给需要跑 java 工具的钩子用

2.2 docker run 启动

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
docker run -d \
  --name gitea \
  --privileged=true \
  --restart=always \
  -e TZ=Asia/Shanghai \
  -e USER_UID=1000 \
  -e USER_GID=1000 \
  -p 13000:3000 \
  -p 10022:22 \
  -v /d/release/gitea:/data \
  gitea/gitea:1.19.0-rc1

坑 1--privileged=true 是为了让钩子脚本能正常运行(pre-receive 等钩子会涉及 chown、chmod)。不用 --privileged 会导致钩子"无法执行"。

2.3 docker-compose 启动

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# docker-compose.yml
version: "3"
services:
  gitea:
    image: gitea/gitea:1.23.5-jdk8
    container_name: gitea
    restart: always
    privileged: true
    environment:
      - TZ=Asia/Shanghai
      - USER_UID=1000
      - USER_GID=1000
    ports:
      - "13000:3000"     # Web
      - "10022:22"       # SSH
    volumes:
      - "/home/docker/gitea/data:/data"

坑 2gitea/gitea:1.23.5-jdk8-jdk8 后缀是给"需要在 Gitea 容器内跑 Java 工具"(如 p3c-pmd)的场景用的。如果你的代码规范检查不在 Gitea 容器内跑,用普通 gitea/gitea:1.23 镜像更小

2.4 关键配置

容器内 /data/gitea/conf/app.ini

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
[server]
DOMAIN = <YOUR_DOMAIN>
SSH_DOMAIN = <YOUR_DOMAIN>
HTTP_PORT = 3000
ROOT_URL = http://<YOUR_DOMAIN>:13000/

[security]
INTERNAL_TOKEN = <RANDOM_32_BYTES>

[service]
DISABLE_REGISTRATION = false
ENABLE_NOTIFY_MAIL = false

[migrations]
ALLOWED_DOMAINS = 127.0.0.1,<INTERNAL_HOST>,<PUBLIC_HOST>,gitee.com
ALLOW_LOCALNETWORKS = true

[oauth2]
ENABLED = true

坑 3ALLOWED_DOMAINS + ALLOW_LOCALNETWORKS=true 是让 Gitea 能从内网 IP 拉代码(clone 公共仓库),不配这个导入外部仓库会报 403。

2.5 创建第一个仓库

Web 端:登录 → +创建新仓库

命令行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 从命令行创建新仓库
mkdir myproject && cd myproject
git init
git checkout -b main
echo "# My Project" > README.md
git add .
git commit -m "first commit"
git remote add origin ssh://git@<GITEA_HOST>:10022/<USERNAME>/myproject.git
git push -u origin main

# 推送已存在的仓库
cd existing-project
git remote add origin ssh://git@<GITEA_HOST>:10022/<USERNAME>/existing-project.git
git push -u origin main

2.6 升级路径

gitea:jdk8gitea:1.23.5-jdk8 这种升级数据完全兼容——直接换镜像 tag 即可:

1
2
3
4
docker-compose down
# 改 docker-compose.yml 的 image tag
docker-compose up -d
# 启动后会自动跑数据库 migration

三、Gitblit 部署

Gitblit 的特点是纯 Java——如果你公司强制 Java 技术栈、或不想在服务器装 Go 运行时,选它。

1
2
3
4
5
# 第三方镜像(Gitblit 官方未提供 Docker 镜像)
docker pull jacekkowalski/gitblit
docker run -d -p 8080:8080 -p 8443:8443 \
  -v /data/gitblit:/opt/gitblit-data \
  jacekkowalski/gitblit

访问 http://<HOST>:8080/,默认账号 admin/admin(首次登录后改密)。

坑 4:Gitblit 镜像社区维护,版本和官方 release 可能有滞后;建议自己 build

1
2
3
4
5
6
FROM openjdk:8-jre
RUN wget https://github.com/gitblit/gitblit/releases/download/v1.9.3/gitblit-1.9.3.tar.gz && \
    tar -xzf gitblit-1.9.3.tar.gz -C /opt && \
    rm gitblit-1.9.3.tar.gz
EXPOSE 8080 8443
CMD ["/opt/gitblit-1.9.3/gitblit.sh"]

四、Gogs 部署

Gogs 是 Gitea 的"前辈"——Gitea 团队最初就是从 Gogs fork 出来的(2016 年 fork 原因是"想更积极地迭代")。

1
2
3
4
5
6
7
docker run -d \
  --name gogs \
  --restart=always \
  -p 13000:3000 \
  -p 10022:22 \
  -v /data/gogs:/data \
  gogs/gogs:0.13

坑 5:Gogs 0.13 是最后的稳定版,2020 年后社区基本被 Gitea 吸引。新项目不建议选 Gogs——除非你正在维护一个老 Gogs 实例(数据迁移到 Gitea 是免费的)。

五、客户端提交前检查:p3c-pmd

“我自己在本地忘了跑规范检查”——这是最常见的"提交违规"原因。解决:把规范检查做成 git pre-commit 钩子,每次 git commit 自动跑

5.1 准备 p3c-pmd 工具

1
2
3
4
5
# 1. 下载 p3c-pmd 项目
git clone https://github.com/alibaba/p3c.git
cd p3c/p3c-pmd
mvn clean package
# 产出: p3c-pmd-2.1.1-jar-with-dependencies.jar

坑 6:必须用 p3c-pmd,不要用 pmd-dist——p3c-pmd 是阿里《Java 开发手册》对应的 PMD 规则集(命名规范、注释规范、OOP 规约、集合处理、并发处理等)。

5.2 手动跑一次(验证工具)

1
2
3
4
java -cp p3c-pmd-2.1.1-jar-with-dependencies.jar net.sourceforge.pmd.PMD \
  -d <PROJECT_DIR> \
  -R rulesets/java/ali-comment.xml,rulesets/java/ali-concurrent.xml,rulesets/java/ali-constant.xml,rulesets/java/ali-exception.xml,rulesets/java/ali-flowcontrol.xml,rulesets/java/ali-naming.xml,rulesets/java/ali-oop.xml,rulesets/java/ali-orm.xml,rulesets/java/ali-other.xml,rulesets/java/ali-set.xml \
  -f text -shortnames -no-cache -min 1

输出形如:

1
2
/home/user/project/src/main/java/com/example/Foo.java:42:    Avoid start with number for class name...
/home/user/project/src/main/java/com/example/Bar.java:88:    Method name should be lowerCamelCase...

5.3 Maven 集成(开发期自动跑)

pom.xml 加:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<properties>
  <maven-pmd-plugin.version>3.21.2</maven-pmd-plugin.version>
  <p3c-pmd.version>2.1.1</p3c-pmd.version>
</properties>
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-pmd-plugin</artifactId>
      <version>${maven-pmd-plugin.version}</version>
      <configuration>
        <verbose>true</verbose>
        <rulesets>
          <ruleset>/rulesets/java/ali-comment.xml</ruleset>
          <ruleset>/rulesets/java/ali-concurrent.xml</ruleset>
          <ruleset>/rulesets/java/ali-constant.xml</ruleset>
          <ruleset>/rulesets/java/ali-exception.xml</ruleset>
          <ruleset>/rulesets/java/ali-flowcontrol.xml</ruleset>
          <ruleset>/rulesets/java/ali-naming.xml</ruleset>
          <ruleset>/rulesets/java/ali-oop.xml</ruleset>
          <ruleset>/rulesets/java/ali-orm.xml</ruleset>
          <ruleset>/rulesets/java/ali-other.xml</ruleset>
          <ruleset>/rulesets/java/ali-set.xml</ruleset>
        </rulesets>
        <printFailingErrors>true</printFailingErrors>
        <minimumPriority>1</minimumPriority>
      </configuration>
      <executions>
        <execution>
          <id>pmd-check-verify</id>
          <phase>package</phase>
          <goals>
            <goal>check</goal>
          </goals>
        </execution>
        <execution>
          <id>pmd-pmd-site</id>
          <phase>site</phase>
          <goals>
            <goal>cpd</goal>
          </goals>
        </execution>
      </executions>
      <dependencies>
        <dependency>
          <groupId>com.alibaba.p3c</groupId>
          <artifactId>p3c-pmd</artifactId>
          <version>${p3c-pmd.version}</version>
        </dependency>
      </dependencies>
    </plugin>

    <!-- 自动安装 git pre-commit 钩子 -->
    <plugin>
      <groupId>io.github.phillipuniverse</groupId>
      <artifactId>githook-maven-plugin</artifactId>
      <version>1.0.5</version>
      <executions>
        <execution>
          <goals>
            <goal>install</goal>
          </goals>
          <configuration>
            <hooks>
              <pre-commit>
                echo running validation build
                exec mvn clean package -DskipTests
              </pre-commit>
            </hooks>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

第一次 mvn package 时,githook-maven-plugin自动把 pre-commit 钩子装到 .git/hooks/pre-commit。之后每次 git commit 都会先跑 mvn clean package,失败就阻止 commit。

坑 7minimumPriority=1 是 PMD 规则的"最高优先级"——只有"严重"问题会被拦下来。如果设为 5,所有问题都拦,开发体验会很差。建议从 1 起步,逐步放开到 2 / 3

5.4 手动安装 pre-commit 钩子(不依赖 Maven)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
echo "[pre-commit] running p3c-pmd check"
java -cp /path/to/p3c-pmd-2.1.1-jar-with-dependencies.jar net.sourceforge.pmd.PMD \
  -d . \
  -R rulesets/java/ali-comment.xml,rulesets/java/ali-concurrent.xml,rulesets/java/ali-constant.xml,rulesets/java/ali-exception.xml,rulesets/java/ali-flowcontrol.xml,rulesets/java/ali-naming.xml,rulesets/java/ali-oop.xml,rulesets/java/ali-orm.xml,rulesets/java/ali-other.xml,rulesets/java/ali-set.xml \
  -f text -shortnames -no-cache -min 1
if [ $? -ne 0 ]; then
  echo "[pre-commit] p3c-pmd found violations, commit rejected"
  exit 1
fi
EOF
chmod +x .git/hooks/pre-commit

六、服务端提交检查:pre-receive 钩子

客户端检查是"自愿"的——开发者完全可以 --no-verify 绕过。真正守门的是服务端 pre-receive 钩子——push 时服务端自动跑,违规直接 reject。

6.1 在 Gitea 容器内装 p3c

Gitea 默认镜像是 Alpine(没有 JDK),所以需要 gitea:1.23.5-jdk8 这个带 JDK8 的版本

把 JDK 复制到 Gitea 容器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
docker cp /usr/local/jdk gitea:/usr/local/
docker exec -it gitea bash

# 容器内: 配置环境变量
vim /etc/profile
export JAVA_HOME=/usr/local/jdk/jdk1.8.0_371
export CLASSPATH=$JAVA_HOME/bin:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
export PATH=.:$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
source /etc/profile

# 容器内: 装 glibc(alpine 镜像需要)
sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
apk update
apk add glibc-2.35-r1.apk
apk add glibc-bin-2.35-r1.apk
apk add glibc-i18n-2.35-r1.apk
ln -sf /usr/glibc-compat/lib/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2

# 验证
java -version

把 p3c-pmd 工具复制到容器

1
2
3
4
5
# 宿主机: 编译 p3c-pmd
cd p3c-pmd
mvn clean package
docker cp p3c-pmd-2.1.1-jar-with-dependencies.jar gitea:/
docker cp rulesets gitea:/var/opt

容器内跑一次(验证):

1
2
3
4
5
java -Dfile.encoding=utf-8 -Dpmd.language=zh \
  -cp /data/p3cjava/p3c-pmd-2.1.1-jar-with-dependencies.jar net.sourceforge.pmd.PMD \
  -d /data/p3cjava/tmp \
  -R /data/p3cjava/rulesets/java/ali-comment.xml,/data/p3cjava/rulesets/java/ali-concurrent.xml,... \
  -f text -shortnames -no-cache

6.2 写 pre-receive 钩子

Gitea 仓库路径:/data/git/repositories/<org>/<repo>.git/hooks/pre-receive

1
chmod 755 /data/git/repositories/<org>/<repo>.git/hooks/pre-receive.d/pre-receive

钩子脚本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/usr/bin/env bash
# /data/git/repositories/<org>/<repo>.git/hooks/pre-receive.d/pre-receive

set -e

# 读取 push 信息
# 格式: <oldrev> <newrev> <refname>
while read oldrev newrev refname; do
  # 找到这次 push 涉及的所有 .java 文件
  if [ "$oldrev" = "0000000000000000000000000000000000000000" ]; then
    # 新分支 push
    files=$(git diff-tree --root --no-commit-id --name-only -r $newrev | grep '\.java$' || true)
  else
    # 普通 push
    files=$(git diff --name-only $oldrev $newrev | grep '\.java$' || true)
  fi

  if [ -z "$files" ]; then
    continue
  fi

  # 复制这些文件到 p3c 检查目录
  mkdir -p /data/p3cjava/tmp
  for f in $files; do
    mkdir -p "/data/p3cjava/tmp/$(dirname "$f")"
    git show "$newrev:$f" > "/data/p3cjava/tmp/$f" 2>/dev/null || continue
  done

  # 跑 p3c-pmd
  result=$(java -Dfile.encoding=utf-8 -Dpmd.language=zh \
    -cp /data/p3cjava/p3c-pmd-2.1.1-jar-with-dependencies.jar net.sourceforge.pmd.PMD \
    -d /data/p3cjava/tmp \
    -R /data/p3cjava/rulesets/java/ali-comment.xml,/data/p3cjava/rulesets/java/ali-naming.xml,... \
    -f text -shortnames -no-cache 2>&1 || true)

  if [ -n "$result" ]; then
    echo "$result"
    echo ""
    echo "p3c-pmd found violations, push rejected"
    exit 1
  fi
done

# 检查通过
exit 0
1
chmod +x /data/git/repositories/<org>/<repo>.git/hooks/pre-receive.d/pre-receive

6.3 黑白名单机制

真实场景:某些"特殊项目"(如 demo 仓库、迁移中的老代码)暂时不想被 p3c 拦截——又不能让所有人手动去改钩子。

思路

  • 黑名单:默认所有项目都不拦截;只有 BLACKLIST 里的项目才走 p3c 检查
  • 白名单:黑名单 + p3c 检查完后,如果在 WHITELIST → 强制通过
  • 用户白名单:某些核心开发(架构师)有"豁免权"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env bash
# /data/git/repositories/<org>/<repo>.git/hooks/pre-receive.d/pre-receive

set -e

REPO_NAME=$(basename $(pwd) .git)   # 仓库名
USER_NAME=$(echo $GL_REPOSITORY | awk -F/ '{print $NF}')

BLACKLIST=("legacy-code" "demo-project" "old-billing")
WHITELIST=("template" "scaffolding")
USER_WHITELIST=("admin" "architect")

# 黑名单检查
should_check=false
for b in "${BLACKLIST[@]}"; do
  if [ "$REPO_NAME" = "$b" ]; then
    should_check=true
    break
  fi
done

if [ "$should_check" = false ]; then
  exit 0
fi

# 用户白名单检查
for u in "${USER_WHITELIST[@]}"; do
  if [ "$USER_NAME" = "$u" ]; then
    echo "[pre-receive] $USER_NAME is in user whitelist, skip p3c check"
    exit 0
  fi
done

# 跑 p3c 检查(见 6.2)
# ...

坑 8GL_REPOSITORY 是 Gitea 内置变量,只有在 Gitea 容器内跑的钩子才能拿到。如果在客户端写钩子用这个变量是空的。

七、常见排错

7.1 Gitea SSH 推送失败 “permission denied”

症状git pushPermission denied (publickey)

排查

  1. 客户端 SSH 密钥是否加到 Gitea(User Settings → SSH Keys
  2. Gitea 容器内 /data/git/.ssh 是否存在
  3. 容器端口映射是否对(10022:22
  4. 客户端 ~/.ssh/config 是否写错了 host 别名

7.2 p3c-pmd 报错 “java not found”

症状:Gitea 容器内跑 p3c 报 java: command not found

解决

  • gitea:1.23.5-jdk8 镜像(自带 JDK)
  • 或手动复制 JDK 进容器并配 JAVA_HOME(见 6.1)

7.3 pre-receive 钩子 “权限拒绝”

症状:钩子写好了,但 push 时没执行。

排查

1
2
3
4
5
6
7
# 1. 钩子文件是否可执行
ls -la /data/git/repositories/<org>/<repo>.git/hooks/pre-receive
# 必须是 -rwxr-xr-x
chmod +x /data/git/repositories/<org>/<repo>.git/hooks/pre-receive

# 2. Gitea 用户是否能写
chown -R git:git /data/git/repositories/<org>/<repo>.git/hooks

7.4 客户端 p3c 跑得慢

症状:每次 git commit 跑 p3c 要 30 秒+。

解决

  • maven-pmd-plugin 自带的 cache 选项
  • 缩小规则集(先只跑 ali-naming.xmlali-comment.xml
  • pre-commit 脚本里加 git diff——只检查本次 commit 修改的文件,不扫全工程
1
2
# 优化后的 pre-commit: 只检查 staged 文件
files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.java$')

八、写在最后

Gitea / Gitblit / Gogs 三选一,绝大多数情况直接选 Gitea——社区活跃、CI 内置、UI 现代、迁移工具完善。配 p3c-pmd 做提交前检查才是真正的"省心"——把代码规范固化到提交链路里,而不是每次都"代码 review 时再吐槽"。

下一步建议:

  • Gitea Actions:和 GitHub Actions 兼容的 CI,仓库根目录 .gitea/workflows/*.yml
  • Gitea 自定义主题:通过 templates/custom/ 改 logo / 配色
  • Gitea → Gitea Federation:跨 Gitea 实例同步仓库(实验特性)

参考资料

使用 Hugo 构建
主题 StackJimmy 设计