在内网搭一个 Git 私有服务器,是很多开发团队迟早要面对的事。一开始代码可能零散放在个人磁盘里,几个人靠压缩包传来传去,等到项目变多、成员变多,才发现分支管理、权限控制、历史追溯全都是一团乱麻。Gitea 正是解决这个问题的轻量级方案——一个用 Go 写的 Git 托管服务,几十兆的二进制文件就能跑起来,装好后团队就能通过浏览器管理仓库、审查代码、跟踪 Issue,相当于在局域网里自建了一个"轻量版 GitHub"。
这篇文章适合所有想在内网搭代码仓库的人:刚接触 Git 服务运维的新手可以照着一步步操作,已经被 GitLab 的庞大资源消耗折腾过、想换轻量方案的开发者也能找到合适的迁移思路。我过去几年在不同环境里部署过好几套 Git 服务,从裸 Git 仓库到 Gitea 都试过,这篇文章会把部署流程、关键配置、权限模型和踩过的坑一次讲透,让你少走弯路。
1. 为什么需要自建 Git 服务器:需求分析与方案选型
1.1 什么场景真正需要"私有" Git 服务
先说实话:不是所有人都需要自建 Git 服务器。如果只是一个人写代码,GitHub、Gitee 这类公有平台的私有仓库完全够用。但当出现下面几种情况时,你就该认真考虑在内网搭一套自己的服务了:
- 代码敏感或合规要求高:某些公司内部项目、实验室课题、外包交付代码不允许上传到外部平台,必须存放在自己可控的服务器上。
- 网络条件受限:内网开发环境与公网隔离,开发者无法顺畅访问外部 Git 平台,推送几十 MB 的分支可能要等半天。
- 多人协作需要统一入口:团队从"共享文件夹 + 压缩包"切换到 Git 工作流,需要一个所有人能访问的统一服务器,而不是某台开发机上的临时目录。
- 需要离线可用:现场开发、应急演练、内网演示环境,没有外网但依然要完整的版本管理能力。
在这些场景下,自建 Git 服务器的价值在于:代码永远留在自己的机器上、访问速度不受公网波动影响、权限和备份策略完全由自己掌控。这不是替代 GitHub,而是在"代码不能出内网"的前提下,把 GitHub 的那套核心体验搬回本地。
1.2 Gitea 对比 GitLab、Gogs 和裸 Git 服务
确定要自建之后,选型是第一步。我在这一环节对比过四个主流方案:Idea 上直接跑裸 Git 仓库加 Gitolite、老牌的 GitLab、Gogs 以及它的继任者 Gitea。直接给结论:小团队和一般内网场景,Gitea 是性价比最高的选择。
| 方案 | 资源占用 | 部署复杂度 | 功能完整度 | 维护活跃度 | 适合场景 |
|---|---|---|---|---|---|
| 裸 Git + Gitolite | 极低 | 较高 | 仅 Git 托管 | 一般 | 极客个人、最小化需求 |
| Gogs | 低 | 低 | 基础功能 | 较低 | 早期轻量场景(已不推荐新用) |
| Gitea | 低(1GB 内存可跑) | 极低(单二进制) | 高(Issue/PR/CI 对接/ Wiki) | 活跃 | 中小团队、内网私有部署 |
| GitLab | 高(官方建议 4GB 起步) | 高(组件多) | 极高(完整 DevOps) | 活跃 | 中大型团队、需要完整 CI/CD 平台 |
选 Gitea 而不是 GitLab,最核心的原因是资源占用与维护成本。我在一台 1 核 1GB 内存的虚拟机上跑过 Gitea,日常内存占用在 200MB 左右,一套 GitLab 在同一台机器上几乎跑不起来。而选 Gitea 而不是 Gogs,则是因为 Gitea 社区更活跃、Bug 修复更快、插件生态更丰富。如果你只需要"能 Git clone/push + 浏览器看代码 + 权限管理",Gitea 就是那个"刚刚好"的工具。
1.3 部署方式选型:二进制、Docker 还是系统包
Gitea 官方提供了三种主流安装路径:二进制包、Docker 镜像、Linux 系统包(apt/yum)。我的建议是:
- 单机 CentOS/Debian 服务器:优先用官方二进制,下载后一个文件就是整个服务,升级时替换文件即可,依赖最少、排查问题最直观。
- 喜欢容器化、机器上已有 Docker 环境:优先用 Docker Compose,配置清晰、数据目录独立、迁移方便。
- 需要自动化装机:可以用系统包配合配置管理工具,但我个人不喜欢系统包方式,因为 Gitea 迭代快,系统源里的版本往往滞后。
下面两套部署方式我分别给出完整步骤,大家按自己的环境选一套执行就行。
2. 环境准备与安装部署全流程
2.1 硬件与系统要求
先泼一盆冷水:Gitea 虽然轻,但不是随便一台机器就能稳定跑。根据我的实战经验,给出一份比较稳妥的最低配置参考:
- CPU:1 核即可,2 核体验更好(并发 clone/push 时不会卡顿)
- 内存:512MB 能跑,1GB 建议值,团队超过 20 人建议 2GB
- 磁盘:按仓库大小估算,建议至少预留"仓库总容量 × 1.5"的空间,另留系统盘余量
- 系统:Linux 为主,官方支持 Windows/macOS 但生产场景几乎都是 Linux;我这里以 Debian/Ubuntu 和 CentOS 为例
提示:很多人忽略磁盘 IO 对 Git 服务的影響。Git 操作是大量小文件的随机读写,机械硬盘上 clone 大仓库会明显感觉慢,有条件尽量用 SSD,哪怕是做个 RAID1 的机械盘也会好很多。
2.2 二进制安装步骤详解
二进制安装是"零依赖"路线,特别适合最小化系统。步骤分四步:准备系统用户和目录、下载二进制、初始化服务、配置系统服务。
第一步,创建专用用户和目录。Git 服务不建议直接用 root 跑,权限太大会成为安全隐患,也不利于后面做备份和权限管理:
# 创建 gitea 系统用户,家目录设为 /var/lib/gitea sudo adduser --system --home /var/lib/gitea --group gitea # 创建标准目录结构 sudo mkdir -p /var/lib/gitea/{custom,data,log} sudo chown -R gitea:gitea /var/lib/gitea第二步,下载二进制文件。到 Gitea 官方下载页找到对应平台的二进制包,这里以 Linux amd64 为例:
# 注意版本号请到官网查最新稳定版,不要用太旧的版本 sudo wget -O /tmp/gitea "https://dl.gitea.com/gitea/1.22.1/gitea-1.22.1-linux-amd64" sudo install -o gitea -g gitea -m 0755 /tmp/gitea /usr/local/bin/gitea第三步,先手动启动一次做首次初始化。这一步不要急着注册系统服务,先用前台模式确认能跑起来,方便直接看日志排错:
GITEA_WORK_DIR=/var/lib/gitea sudo -u gitea /usr/local/bin/gitea web -p 3000看到类似Listen: http://0.0.0.0:3000的日志,说明服务启动成功。Ctrl+C 停掉,进入第四步,配置系统服务,让它在开机后自动运行。以 systemd 为例:
sudo tee /etc/systemd/system/gitea.service << 'EOF' [Unit] Description=Gitea (Git with a cup of tea) After=network.target Requires=network.target [Service] User=gitea Group=gitea WorkingDirectory=/var/lib/gitea Environment=GITEA_WORK_DIR=/var/lib/gitea ExecStart=/usr/local/bin/gitea web -p 3000 Restart=always RestartSec=2 [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reload sudo systemctl enable --now gitea到这里,Gitea 服务已经在 3000 端口跑起来了。访问http://服务器IP:3000就能看到安装引导页。需要注意的是,如果你改了 SSH 默认端口(后面会讲),systemd 的 ExecStart 里也可以追加--ssh-port 2222参数,让 Gitea 的 SSH 服务绑定到指定端口。
2.3 Docker Compose 部署方式
已经在用 Docker 的环境,用 Compose 管理 Gitea 会更省心。数据目录通过挂载卷持久化,升级就是拉新镜像重新 create,不需要关心二进制版本。我实际使用的 Compose 配置如下:
version: "3" services: gitea: image: gitea/gitea:1.22.1 container_name: gitea environment: - USER_UID=1000 - USER_GID=1000 - GITEA__server=SSH_PORT=2222 - GITEA__server=DOMAIN=192.168.1.100 - GITEA__server=ROOT_URL=http://192.168.1.100:3000/ restart: always volumes: - ./gitea:/data - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro ports: - "3000:3000" - "2222:2222"用 Docker 部署有三个必须留意的点:
- 权限一致性:容器内的
USER_UID=1000要和宿主机上挂载目录属主的 UID 一致,否则容器写./gitea目录会出现 Permission denied。 - SSH 端口映射:宿主机 2222 映射到容器 22,后面的
GITEA__server=SSH_PORT=2222是告诉 Gitea"外部用户要用 2222 端口连 SSH",这个必须和端口映射对上,否则克隆地址会出错。 - ROOT_URL 要写对:如果客户端是通过 IP 访问,就写
http://IP:3000/;如果将来有域名,最好一开始就规划好,因为后期改 ROOT_URL 会影响仓库的克隆地址显示,老用户可能困惑。
启动命令就一行:
docker compose up -d这套配置里,所有数据都在宿主机./gitea目录下,备份时整个目录拷走即可。相比二进制方式,Docker 的维护确实更省心,代价是多了一层容器网络的概念,新手排错时要多考虑端口映射问题。
3. 初始化配置与核心功能落地
3.1 首次访问、管理员账号与数据库选择
浏览器打开 Gitea 页面,会进入安装配置界面。这里有几个关键选项直接决定后续使用体验,我逐一说明。
数据库选择是第一个选择题。Gitea 支持 SQLite、MySQL、PostgreSQL 三种。我的经验是:
- 团队 10 人以下、仓库量不大:直接选 SQLite,零额外依赖,一个文件搞定,备份也简单。
- 团队较大、准备认真运维:选 MySQL 或 PostgreSQL,并发写入和可靠性更好,前提是你对数据库本身有维护能力。
我见过不少人在这里犹豫半天,其实没必要。Gitea 官方对 SQLite 的支持足够成熟,中小团队完全不需要为一个代码托管工具单独维护一套数据库。如果以后真需要迁移到 MySQL,Gitea 后台也有迁移工具可以操作,但麻烦程度不低,所以一开始想清楚就行。
管理员账号在安装页面上可以直接创建。这里提醒一点:管理员账号不要用团队公共名称如 admin/root 这种,建议用个人真实用户名,方便审计操作记录。
服务域名与基础 URL:这两个字段决定了仓库的 HTTPS/SSH 克隆地址怎么生成。内网环境直接填服务器 IP,例如http://192.168.1.100:3000/。如果服务器后面要接域名,建议在安装时就规划好,减少后期调整的连锁影响。
3.2 SSH 访问与 HTTPS 证书配置
Gitea 的 Web 端可以做很多操作,但团队日常使用 Git 命令行时,SSH 通道是主要入口。默认 Gitea 会尝试绑定 22 端口,问题是很多服务器上 22 已经被系统 SSH 服务占用了。解决方案就是给 Gitea 指定一个独立端口,比如 2222,然后在每个用户的克隆地址里体现。
端口绑定设置位于配置文件app.ini的[server]节:
[server] SSH_PORT = 2222 SSH_LISTEN_PORT = 2222改完后重启 Gitea,再进入用户后台添加 SSH 公钥:
# 在客户端生成密钥(如果还没有) ssh-keygen -t ed25519 -C "你的备注" # 查看公钥内容 cat ~/.ssh/id_ed25519.pub把公钥粘贴到"设置 -> SSH 密钥"里保存。之后客户端要配置一个 config 片段,避免每次指定端口:
# 修改 ~/.ssh/config Host gitlab-internal HostName 192.168.1.100 Port 2222 User git IdentityFile ~/.ssh/id_ed25519这样就可以用git clone git@gitlab-internal:用户名/仓库名.git来克隆,命令简洁不少。关于 HTTPS 证书,内网环境一般没有可信 CA 签发的证书,两种选择:一是自己生成自签名证书并在客户端系统里安装信任,二是直接用 HTTP 加上内网访问边界控制。我建议小团队在纯内网里先用 HTTP,把精力聚焦在核心功能上;等有公网访问需求时再通过反向代理统一加 HTTPS。
注意:Gitea 对 SSH 协议的支持是完备的,但前提是服务器上要装了 Git。CentOS 系机器用
yum install git -y,Debian 系用apt install git -y,装完确认git --version有输出,否则 SSH 通道会报 "fatal: repository not found" 但问题根本不在权限。
3.3 用户、组织与仓库权限设置
内网 Git 服务的权限模型,是团队协作中最容易混乱的部分。Gitea 的权限体系分三层:用户、组织、仓库,理解这三层能避免很多"为什么我看不到那个仓库"的问题。
用户层面:管理员可以在后台创建用户,也可以开放注册。内网环境建议关闭自动注册,由管理员统一创建账号,这样能避免不是团队的人混进来。
组织层面:当项目不止一个时,建议建组织。比如公司有"前台组"和"后台组",就分别建两个组织,然后在组织里建仓库。组织的好处是权限可以批量管理——成员一进组,自动获得组内所有仓库的对应权限,不用逐个仓库添加人。
仓库权限:Gitea 支持"读"、"写"、"管理"三级权限。我给个实用建议:核心分支(main/master)默认只给少数人管理权限,其他人通过 Pull Request 提代码。这样做一开始会有点麻烦,但能有效防止有人在主分支上直接乱搞、或把自己的半成品推到主干。
管理员在后台"站点管理 -> 用户 -> 编辑"里可以为每个用户调整权限,组织管理页也可以设置成员角色。实际运维中,我习惯把权限控制写进一份团队文档里,谁在哪个项目什么角色一目了然,新成员加入时照着文档开权限就行,不用每次口头交代。
3.4 关键配置文件 app.ini 解读
Gitea 的配置文件是app.ini,二进制安装时通常放在/var/lib/gitea/custom/conf/app.ini,Docker 安装在容器内/data/gitea/conf/app.ini。虽然 Web 后台能配置大部分内容,但有些关键参数只有改配置文件才生效。我把常用的几个节列出来:
[server] PROTOCOL = http DOMAIN = 192.168.1.100 ROOT_URL = http://192.168.1.100:3000/ HTTP_PORT = 3000 SSH_PORT = 2222 SSH_LISTEN_PORT = 2222 LFS_START_SERVER = true [database] DB_TYPE = sqlite3 PATH = /var/lib/gitea/data/gitea.db [repository] DEFAULT_PRIVATE = private MAX_CREATION_LIMIT = 0 DEFAULT_BRANCH = main [ui] EXPLORE_PAGESIZE = 20 [service] DISABLE_REGISTRATION = true REQUIRE_SIGNIN_VIEW = true [mailer] ENABLED = false [openid] ENABLE_OPENID_SIGNIN = false几个参数说明一下:
LFS_START_SERVER = true:开启 Git LFS 支持。如果团队仓库里要放设计稿、二进制包这类大文件,这个必须开,否则仓库会越来越臃肿。DISABLE_REGISTRATION = true:禁止自助注册,配合管理员统一建号。REQUIRE_SIGNIN_VIEW = true:未登录用户不能查看任何内容,这是内网私有仓库的基本要求。DEFAULT_PRIVATE = private:默认新仓库是私有,避免有人误建公开仓库后代码泄露。
每次改完app.ini,记得重启 Gitea 让配置生效:systemctl restart gitea或docker restart gitea。
4. 进阶玩法:Webhook、备份迁移与日常维护
4.1 Webhook 对接 CI/CD 和消息通知
Gitea 虽然内置了简单的 CI(Gitea Actions),但很多团队在初期会用它和已有的 CI 系统对接。这时候 Webhook 是主角。Webhook 的配置入口在"仓库设置 -> Web 钩子",可以设置"推送后触发"、"创建分支后触发"、"Issue 变动后触发"等事件。
一个典型的用法是:每次推送到 main 分支后,Gitea 向 CI 系统发送一个 POST 请求,CI 收到后自动拉取代码、执行测试、构建产物。配置时要注意两点:
- Webhook 的 URL 必须是 CI 系统可达的地址,内网环境直接写
http://CI服务器IP/trigger即可。 - 密钥(Secret)要配对好,CI 端要校验签名,防止内网中有人伪造请求触发构建。
我在实际使用中还让 Gitea 的 Webhook 对接过企业微信群机器人,推送代码后自动发一条消息,带上提交者、提交信息和 commit 链接,团队的代码动态一目了然。这个操作在"仓库设置 -> Web 钩子 -> 添加 Webhook"里,选择类型为"企业微信/Gitee/自定义"均可,自定义的话把机器人 Webhook 地址填进去,消息格式按对应文档写一个 JSON 模板就行。
4.2 数据备份与恢复
搭好服务之后,"备份"可能是最容易被忽略、一旦出事最痛的事。Git 仓库本身有分布式特性,每台开发机上都有全量副本,但 Gitea 的数据库里还有 Issue、评论、用户信息、Webhook 配置,这些丢了很难恢复。
我的备份方案是三部分一起备份:
- 数据库文件:SQLite 的话直接拷贝
/var/lib/gitea/data/gitea.db;MySQL 用mysqldump。 - 仓库裸目录:默认在
/var/lib/gitea/data/repositories,这里面是所有仓库的 git 数据。 - 配置文件:
app.ini、以及 LFS 对象目录data/lfs。
最简单的备份脚本骨架:
#!/bin/bash BACKUP_DIR="/backup/gitea_$(date +%Y%m%d)" mkdir -p "$BACKUP_DIR" # 停服务避免写数据(可选,高频场景可用文件系统快照代替) systemctl stop gitea cp -r /var/lib/gitea/data "$BACKUP_DIR/data" cp /var/lib/gitea/custom/conf/app.ini "$BACKUP_DIR/app.ini" systemctl start gitea # 只保留最近 7 天备份 find /backup -name "gitea_*" -mtime +7 -exec rm -rf {} \;关于备份频率,团队活跃的话建议每天一次全量备份。Git 仓库体积通常不大,几个 GB 都算多了,一天一次完全扛得住。恢复的时候,把数据库文件和 repositories 目录放回原位置,改好属主、起服务即可。这一步一定要在干净环境里提前演练一次,我见过太多"以为备份了,恢复时才发现问题"的情况。
4.3 日常维护与升级
Gitea 的升级路径非常平滑:下载新版二进制、替换旧文件、重启服务,Gitea 会自动迁移数据库结构。用 Docker 就更简单,拉新镜像重启容器。但升级前有一条铁律:先备份,再升级。Gitea 几乎每个版本都有数据库结构变更,万一你跳过了几个大版本,迁移脚本可能兼容不上。
日常维护我还有几个习惯:
- 每周看一眼磁盘用量,仓库增长速度超预期时及时排查是谁在推大文件。
- 定期清理超过一年的临时分支和合并后的分支,避免仓库列表堆积。
- 关注 Gitea 官方公告,遇到安全更新尽快升级。内网不代表绝对安全,尤其是服务器还能被部分外部人员访问时。
5. 常见问题排查与避坑实录
5.1 SSH 连不上的几种典型原因
SSH 问题占了我排障记录的半数以上,而且原因通常很隐蔽。我整理了几个高频场景:
场景一:Permission denied (publickey)
这个提示最直接的原因是公钥没配对或没生效。按顺序排查:
- 确认你用的密钥是否已添加到 Gitea 个人设置里,注意区分是添加到用户还是仓库,权限不对也一样拒绝。
- 确认客户端用的是 ed25519 或 RSA 类型,老一点的系统默认生成 RSA,但密钥没加入 ssh-agent 也会拒绝。
- 在 Gitea 后端执行
su - gitea -s /bin/bash后手动 clone 一次,如果服务端本地能通,问题基本在客户端特定配置上。
场景二:fatal: could not read Username for 'http://...'
这是走 HTTP 协议时没带用户名密码。解决办法一是用 SSH 协议替代,二是在.git/config里补全带凭据的 URL,或者用凭据管理器缓存密码。内网环境为了安全,我还是推荐 SSH。
场景三:换端口后人人都 clone 失败
如果你把 SSH 端口从 22 改成 2222,但 Web 端的克隆地址没更新,用户复制的命令就会连错端口。改完SSH_PORT后,确认站点管理里的"本服务信息"页显示的新端口,再让团队重新复制克隆地址。
5.2 端口、防火墙与反向代理问题
内网部署最容易被"防火墙挡掉"。装完 Gitea 后发现浏览器打不开页面,十有八九是系统防火墙没有放行端口:
# CentOS/RHEL 系 sudo firewall-cmd --permanent --add-port=3000/tcp sudo firewall-cmd --permanent --add-port=2222/tcp sudo firewall-cmd --reload # Debian/Ubuntu 系 sudo ufw allow 3000/tcp sudo ufw allow 2222/tcp如果 Gitea 前面还挂了 Nginx 反向代理,要特别注意ROOT_URL里写的协议。Nginx 上终止 HTTPS 时,ROOT_URL应该写https://域名/而不是http://IP:3000/,否则 Web 端生成的资源链接会指向错误的协议和端口,页面能打开但样式全丢、克隆地址不对。我当时第一次接 Nginx 时就踩了这个坑,排查了半小时才发现是 URL 生成逻辑问题,而不是 Nginx 配置错误。
5.3 数据库与存储优化经验
SQLite 模式用久了,一个明显的优化点是定期执行VACUUM回收磁盘空间,尤其是频繁删建仓库后。Gitea 自带的gitea doctor命令是个宝藏,可以检查数据库健康、修复仓库记录问题、清理孤儿数据:
sudo -u gitea GITEA_WORK_DIR=/var/lib/gitea /usr/local/bin/gitea doctor check另外,仓库目录里.git对象文件如果过于分散,可以执行git gc来压缩对象。对长期活跃的仓库,建议设定 Gitea 后台的 GC 策略。我自己会在每周日凌晨跑一个脚本,对近期有提交的仓库执行git gc --aggressive --prune=now,能显著降低仓库体积和 clone 时间。
5.4 一些容易被忽略的小细节
最后分享几个我在真实运维中碰到的小问题,单个看着不起眼,但加起来很影响体验:
- 系统时区不对:Gitea 的提交时间显示和使用者本地时间不一致,排查半天发现服务器时区是 UTC,设置成
Asia/Shanghai后就正常了。 - 仓库名大小写:Git 对仓库路径是大小写敏感的,创建仓库时如果习惯了
MyProject这种写法,克隆地址一定不能写错大小写,很多"404 仓库不存在"其实是路径写错。 - 服务器时钟不同步:如果机器没有配置 NTP 时间同步,多个开发机之间提交时间会出现错乱,合并代码检查历史记录时非常混乱,建议内网服务器统一配置时间同步。
- LFS 但没有配置对象存储:LFS 默认把大文件也存在数据目录里,备份时如果把 LFS 目录漏了,恢复后历史大文件会丢失,这个比普通仓库数据丢失还难发现。
我在内网跑 Gitea 的这几年,最大的体会是:这类工具的价值不在于功能多炫,而在于"稳定、不折腾"。只要把上面这些基础配置和备份习惯做好,它能在角落里默默跑好几年不用你操心。如果你团队还在用共享文件夹传代码,不妨花一个下午把 Gitea 搭起来,从第一个仓库成功 clone 的那一刻起,你就再也不想回到压缩包时代了。真要说还有什么进阶方向,等用熟练了可以考虑把 Gitea Actions 也跑起来,把构建、测试都收到内网里,那整个开发流就闭环了。