本篇文章聊聊搭建“轻量好用又不吃资源”的代码仓库,分享如何在 Docker 环境下搭配 Traefik v3 快速搭建一个私有化的 Gitea 代码仓库。

写在前面

距离我第一篇 Gitea 使用指南《使用 Docker 和 Traefik v2 搭建轻量代码仓库(Gitea)》已经过去了五年。当时文章中用的 Gitea 版本还是 1.10,现在项目已经升级到了 1.20+,功能和配置都有了不少变化。

当然,如果你只是有临时的、通过 Git 传输文件或触发 WebHook 的需求,可以使用《轻量安全的部署方案》中提到的“Git Over SSH”方案。至于 GitLab,我个人并不太推荐。因为资源消耗倒是次要的,但频繁出现的安全问题和告警,确实会分散精力,浪费时间在没有技术收益的事情上。

如果你确实需要 GitLab,也可以参考我之前分享的十多篇 GitLab 相关经验文章

准备工作

我们需要做以下几项准备:Docker 环境、Traefik 容器部署、HTTPS 证书,以及选择合适的 Gitea 容器镜像。

Docker 环境

Develop Faster. Run Anywhere

Docker 环境的准备非常简单:

Traefik 的使用

关于 Traefik,这几年我写了很多文章介绍它。2023年,它正式升级到了 3.0 版本,配置与 1.0 和 2.0 相比有了较大变化。

如果你想深入了解,可以阅读《Traefik v3.0 Docker 全面使用指南:基础篇》;想快速上手的话,可以从《Docker 环境下使用 Traefik 3 的最佳实践:快速上手》开始。

进一步,还可以阅读《Docker 环境下使用 Traefik v3 和 MinIO 快速搭建私有化对象存储服务》,了解如何通过“统一网关”和“域名”而非各种奇怪的端口号来提供服务,加深对 Traefik 使用的理解掌握。

HTTPS 证书

如果已有证书可以直接使用。没有的话,也可以:

  • 使用自签名证书(参考《Docker 环境下使用 Traefik 3 的最佳实践:快速上手》中的“启动 Traefik 并自动申请 HTTPS 证书”部分)
  • 购买域名后申请免费的 Let’s Encrypt 证书使用

我个人建议至少使用自签名证书,不仅安全性更高,还能使用许多依赖 HTTPS 的性能优化功能。而且如果是内部或个人访问相关服务,也少了每年都需要更新更换证书的维护成本。

Gitea 的容器镜像

目前 Gitea 的容器镜像主要托管在 DockerHub 上,官方提供了linux/amd64linux/arm64 两种架构的镜像,可能和受众主要是生产环境使用相关。相比之下,它的前身 Gogs 还支持 linux/arm/v7,能运行在树莓派等更廉价的设备上,对于爱好者和轻量化运行更友好一些。

要获取 Gitea 最新的 1.23.5 版本,我们可以执行:

docker pull gitea/gitea:1.23.5

但是,由于 Gitea 代码对于容器实现的偏好问题,如果我们使用上面的使用 root 运行的镜像,当我们想备份或还原 Git 项目数据的时候,就会遇到一堆问题:因为项目缺少了 rootless 版本的初始化程序 go-gitea/gitea/docker/rootless/usr/local/bin/docker-setup.sh

所以,更推荐的镜像是 rootless 版本:

docker pull gitea/gitea:1.23.5-rootless

顺便说一句,如果条件允许,建议直接从 Docker Hub 下载,而不是镜像源。这样可以帮助维护团队获得更准确的用户数据统计和无声的支持到这类产品团队,这个小的动作对这些个华人团队,从开源免费软件向开源商业软件转型的团队会有所帮助。

在开源软件世界里,Docker Hub Pulls 数量(数据指标),对于在做开源商业化的公司,是一种和 GitHub Stars 类似的社区认可。(或许有一些海外科技投资人会介意吧)虽然这个团队相对低调,但是从“企业版”说明文档和各种散落的公开信息中,能够看得出的是这个华人团队在努力的将开源免费软件转向开源商业软件。他们吸取和改进了 GitLab 发展过程中的许多“运营失误”,在上面的企业版文章中,明确提出并不排斥用户转向商业化版本,然后再重返开源版本。

如果使用上面的命令访问官方镜像比较麻烦,也可以通过一些加速镜像库来完成软件镜像的下载,这里就就不必纠结啦。

开始部署

在准备工作都就绪之后,我们就可以开始进行部署了。

声明配置文件

我们先来创建一个配置文件 .env,包含了绝大多数运行软件,需要我们干预的配置:

# 应用名称
SERVICE_NAME=Lab Gitea
# 服务域名
SERVICE_DOMAIN=gitea.lab.com
# 使用的应用镜像
DOCKER_IMAGE=gitea/gitea:1.23.5-rootless
# 允许"公网",跨主机访问 Git SSH Server
SSH_PORT_EXPOSE=0.0.0.0:22

根据你的需求,完成上面配置的内容后,我们继续完成 docker-compose.yml 的配置编写:

name: gitea

services:

  gitea:
    image: ${DOCKER_IMAGE}
    container_name: ${SERVICE_DOMAIN}   
    ports:
      - ${SSH_PORT_EXPOSE}:22
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - APP_NAME=${SERVICE_NAME}
      - RUN_MODE=prod
      - RUN_USER=git
      - SSH_DOMAIN=${SERVICE_DOMAIN}
      - SSH_PORT=22
      - SSH_LISTEN_PORT=22
      - HTTP_PORT=80
      - ROOT_URL=https://${SERVICE_DOMAIN}
      - LFS_START_SERVER=true
      - REQUIRE_SIGNIN_VIEW=true
      - DB_TYPE=sqlite3
      - INSTALL_LOCK=false
      - DISABLE_GRAVATAR=true
    networks:
      - traefik
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"
      - "traefik.http.routers.giteaweb.middlewares=redir-https"
      - "traefik.http.routers.giteaweb.entrypoints=http"
      - "traefik.http.routers.giteaweb.rule=Host(`${SERVICE_DOMAIN}`)"
      - "traefik.http.routers.giteassl.middlewares=gzip"
      - "traefik.http.routers.giteassl.entrypoints=https"
      - "traefik.http.routers.giteassl.tls=true"
      - "traefik.http.routers.giteassl.rule=Host(`${SERVICE_DOMAIN}`)"
      - "traefik.http.services.giteabackend.loadbalancer.server.scheme=http"
      - "traefik.http.services.giteabackend.loadbalancer.server.port=80"
    volumes:
      # 标准 Linux 系统下使用
      # - /etc/localtime:/etc/localtime:ro
      # - /etc/timezone:/etc/timezone:ro
      - ./config:/etc/gitea:rw
      - ./data:/var/lib/gitea:rw
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
    extra_hosts:
      - "${SERVICE_DOMAIN}:127.0.0.1"
    healthcheck:
      test: ["CMD-SHELL", "wget -q --spider --proxy off localhost || exit 1"]
      interval: 5s

networks:
  traefik:
    external: true

将上面两个文件保存好之后,我们还需要做一些基础的准备工作,完成数据和配置目录的创建:

mkdir config
mkdir data
chown 1000:1000 config
chown 1000:1000 data

接着,使用 docker compose up -d 启动服务:

# docker compose up -d

[+] Running 1/1
 ✔ Container gitea.lab.com  Started

稍等几秒,使用 docker compose ps,可以查看服务是否正常运行:

# docker compose ps

NAME            IMAGE                                              COMMAND                  SERVICE   CREATED          STATUS                    PORTS
gitea.lab.com   gitea/gitea:1.23.5-rootless   "/usr/bin/dumb-init …"   gitea     31 seconds ago   Up 30 seconds (healthy)   2222/tcp, 0.0.0.0:22->22/tcp, 3000/tcp

当看到服务运行状态标记为 healthy,我们就可以通过浏览器打开 Gitea ,来进行初始化安装了。

初始化安装

如果你是私有化使用,可以只完成管理员的配置,就可以开始使用了。

软件初始化过程

当配置好管理员信息后,软件会进行初始化,初始化完毕后,我们就能够看到一个酷似早期 GitHub 界面的网站啦。(Gogs、Gitea 早期借鉴了 GitHub 主题风格)

安装完毕的 Gitea 界面

配置 SSH Keys 进行数据交互

为了方便日常的使用,以及避免将“账号”、“密码”写在各种 CI 配置或环境变量中,我们可以在账号中添加 SSH Key。

配置你的 SSH Keys

在界面中配置好之后,我们可以使用 ssh -T 命令进行可访问性验证:

ssh -T git@gitea.lab.com

命令执行之后,我们会收到类似下面的提示信息,输入 “yes” 后,就能够看到来自 Git 服务器的问候信息了。

# ssh -T git@gitea.lab.com

The authenticity of host 'gitea.lab.com (10.11.12.13)' can't be established.
RSA key fingerprint is SHA256:123456/123/12345.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'gitea.lab.com' (RSA) to the list of known hosts.


Hi there, soulteary! You've successfully authenticated with the key named local-key, but Gitea does not provide shell access.
If this is unexpected, please log in with password and setup Gitea under another user.

接下来,我们就能够通过 git clone 项目地址,直接畅快的下载或更新私有项目中的代码,而不需要配置或输入账号密码啦。

服务基础维护

只要运行设备是可靠的,默认情况下这个服务不需要做任何维护干预。如果设备出现异常停止,建议使用 docker compose logs 查看具体的出错原因。

在过去的五年使用过程中,除了官方升级软件出现问题之外,在各种环境下,我只遇到过下面几个情况:

  • 测试新设备时:NVMe 磁盘过热掉盘,导致磁盘不可写入
  • 容器限制最大写入:磁盘写满
  • 软件老版本存在问题:备份写满磁盘
  • 不带 UPS 或电池的设备异常断电
  • Docker 本身出现问题,导致服务异常退出

通常情况下,只要没有出现数据损坏的问题,使用下面的命令重置容器环境就能够完成服务的重新启动,或者修改配置后的升级操作:

docker compose down && docker compose up -d

是不是非常省心呢。

数据的备份和还原

基于上面的配置文件,我们可以使用下面的命令,来快速完成数据备份:

docker compose exec -u git gitea gitea dump

命令运行完毕,则会出现类似下面的日志:

2025/03/16 22:43:48 ...s/storage/storage.go:176:initAttachments() [I] Initialising Attachment storage with type: local
2025/03/16 22:43:48 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /var/lib/gitea/data/attachments
2025/03/16 22:43:48 ...s/storage/storage.go:166:initAvatars() [I] Initialising Avatar storage with type: local
2025/03/16 22:43:48 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /var/lib/gitea/data/avatars
2025/03/16 22:43:48 ...s/storage/storage.go:192:initRepoAvatars() [I] Initialising Repository Avatar storage with type: local
2025/03/16 22:43:48 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /var/lib/gitea/data/repo-avatars
2025/03/16 22:43:48 ...s/storage/storage.go:186:initLFS() [I] Initialising LFS storage with type: local
2025/03/16 22:43:48 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /var/lib/gitea/git/lfs
2025/03/16 22:43:48 ...s/storage/storage.go:198:initRepoArchives() [I] Initialising Repository Archive storage with type: local
2025/03/16 22:43:48 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /var/lib/gitea/repo-archive
2025/03/16 22:43:48 ...s/storage/storage.go:208:initPackages() [I] Initialising Packages storage with type: local
2025/03/16 22:43:48 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /var/lib/gitea/packages
2025/03/16 22:43:48 ...s/storage/storage.go:219:initActions() [I] Initialising Actions storage with type: local
2025/03/16 22:43:48 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /var/lib/gitea/actions_log
2025/03/16 22:43:48 ...s/storage/storage.go:223:initActions() [I] Initialising ActionsArtifacts storage with type: local
2025/03/16 22:43:48 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /var/lib/gitea/actions_artifacts
2025/03/16 22:43:48 cmd/dump.go:172:runDump() [I] Dumping local repositories... /var/lib/gitea/git/repositories
2025/03/16 22:43:48 cmd/dump.go:217:runDump() [I] Dumping database...
2025/03/16 22:43:48 cmd/dump.go:229:runDump() [I] Adding custom configuration file from /etc/gitea/app.ini
2025/03/16 22:43:48 cmd/dump.go:244:runDump() [I] Custom dir /var/lib/gitea/custom is inside data dir /var/lib/gitea, skipped
2025/03/16 22:43:48 cmd/dump.go:256:runDump() [I] Packing data directory.../var/lib/gitea
2025/03/16 22:43:48 cmd/dump.go:335:runDump() [I] Finish dumping in file /var/lib/gitea/gitea-dump-1742136228.zip

因为我们将容器中的数据目录持久化在了宿主机,所以我们可以在 data 目录直接看到备份文件:

# ls data/gitea-dump-1742136228.zip 
data/gitea-dump-1742136228.zip

你可以结合之前文章提到的 “Cronicle” 来对服务进行日常备份,确保重要的数据不会出现损失啦。

最后

本篇文章先聊到这里,后面相关的文章里,继续展开下新版本 Gitea 的其他使用方案,包括使用它作为常见软件源的私有化仓库,以及古老版本的容器数据如何进行迁移和数据恢复(实例升级)。

–EOF