做技术这几年,我帮团队和个人搭过至少五套代码托管服务,从最基础的裸仓库加 Git 钩子,到完整带界面的 Gitea,再到那些动辄吃掉几个 G 内存的重量级全栈方案,折腾一圈下来的结论是:如果只是想给团队内部搞一套顺手、不用花太多心思维护的 Git 服务,Gitea 基本就是最优解。这套由 Go 写的轻量级 Git 托管平台,单二进制文件就能跑起来,内存占用常年压在几百 MB 以内,却把日常开发最常用的那套能力——仓库管理、Issue、Pull Request、Webhook、Release、LFS——都覆盖得七七八八。这篇文章不打算讲高深原理,也不推销花哨功能,就老老实实把 Gitea 从安装部署、仓库操作、权限管理到自动化联动和日常维护的基本操作梳理一遍,给正在选型或者刚装完不知道从哪下手的同学一份能照着做的参考。1. 为什么我最终选了 Gitea:一台旧服务器也能跑起的 Git 服务1.1 轻量是 Gitea 最打动人的地方Gitea 本质上是 Go 语言编译出的单个可执行文件,运行时负责 HTTP 服务、SSH 代理、Git 操作调度,外部依赖只有一个小型数据库(SQLite 或者 PostgreSQL / MySQL)。和那些要把多个子服务拆开、还要附带队列和监控组件的全栈方案不同,Gitea 开箱就是一个进程加一个数据目录。我实测过:一台 1 核 2G 的云服务器,装完 Gitea 之后空闲内存大概在 300 到 500MB 之间波动,日常跑十来个仓库毫无压力。同样规模的需求放在重量级方案上,光初始化和后台任务就可能吃满内存,小机器基本戴不动。轻量带来另一个实际好处是迁移省事。我搬过几次服务器,Gitea 的迁移就三步:停服务、打包数据目录、到新机器解包。配置、仓库数据、数据库文件全在一个目录下,不需要像复杂方案那样协调多个组件的一致状态,这一点在应急恢复时非常救命。1.2 功能覆盖:小团队 80% 的场景它都够用有些人会质疑,Gitea 毕竟是轻量定位,日常够不够用?我的答案是:小型团队最常用的那套流程它全都有。代码托管部分支持 SSH 和 HTTPS 两种克隆方式,仓库可以建私有、公开或者组织内可见;协作部分有 Issue、里程碑、看板、Pull Request、代码审查评论;发布部分有 Tag、Release,还能在 Release 页面挂构建产物;自动化部分有内置 Actions、Webhook 可以联动外部系统。对于一个二三十人的团队,这些功能不仅够用,而且够顺。我见过不少团队把 Gitea 装完之后,唯一需要外接的只剩制品库和容器镜像仓库,那属于边界需求,不影响核心使用。真正的日常工作——写代码、提 PR、评审、发版——在 Gitea 里可以闭环完成。1.3 它明显不适合的场景也要说清楚当然也要说实话,Gitea 不打算和重量级平台正面竞争的那部分,恰恰是它最短的板。如果团队需要重量级的审计合规、精细到目录级别的权限控制,或者重度依赖复杂 CI 插件生态,Gitea 会吃力不少。官方内置的 Actions 还在持续完善,和成熟 CI 相比,插件丰富度、缓存机制、并发调度的能力都有差距。我自己的建议是:项目主要是内部代码管理和团队协作,Gitea 是省心之选;如果有强制合规要求,再认真评估商业方案。2. 部署篇:下载一个二进制文件,四个步骤跑起来2.1 部署方式选择:二进制包优先于容器Gitea 的部署方式无非三种:官方二进制包、Docker 镜像、系统包管理器安装。我个人的推荐顺序是:二进制包优先,Docker 第二,系统包管理器第三。二进制包最直接的优点是确定性。从官方下载站拿到对应平台的压缩包,解压出来就是gitea一个文件,放到/usr/local/bin就能执行。没有镜像层、没有容器网络抽象,出了任何问题都好排查。Docker 方式适合本来就有容器编排环境的人,但要注意数据卷的权限问题——容器里默认以git用户跑,宿主机挂载目录的属主如果不对,就会出现“目录明明有权限却写不进去”的诡异情况。系统包管理器的版本往往滞后,打包习惯也不统一,我不建议在生产环境用。无论选哪种,核心都是三步:下载二进制、准备数据目录、写配置文件。下面按最省事的二进制方式展开。2.2 app.ini 里我会手动确认的五个配置项Gitea 的配置文件在工作目录下的custom/conf/app.ini,安装包自带默认配置,但有五个地方我每次都会手动确认:配置项作用常见错误RUN_USER运行进程的系统用户用 root 跑,权限过大ROOT_URL对外服务的完整地址漏掉斜杠或端口,克隆地址跟着错HTTP_PORTHTTP 监听端口默认 3000,容易被反代忘记转发SSH_PORTSSH 对外端口默认 22,和系统 SSH 撞车DOMAIN克隆地址里的主机名填了内网 IP 又想用域名访问其中最阴的坑是ROOT_URL没写对。比如我在内网用 IP 加端口访问,ROOT_URL写成http://192.168.1.10:3000/才正确;如果前面挂了 Nginx 做 HTTPS 域名转发,这里又要改成https://git.example.com/。这个配置错了之后,网页上复制出来的克隆地址会出现端口不对或者协议不对的问题,初学者经常被绕半天。2.3 systemd 托管与开机自启的关键细节二进制方式本地运行很简单,./gitea web就能起来,但放在服务器上肯定得托管成服务。我一般写这样一个 systemd unit:[Unit] Description=Gitea After=network.target [Service] Type=simple User=git Group=git WorkingDirectory=/var/lib/gitea ExecStart=/usr/local/bin/gitea web --config /var/lib/gitea/custom/conf/app.ini Restart=always RestartSec=5 [Install] WantedBy=multi-user.target几个细节值得说。Restart=always保证进程崩了自动拉起,但配合RestartSec=5可以避免因数据库锁或配置错误导致反复重启把 CPU 打满。Type=simple是默认值,Gitea 的主进程就是服务本身,不需要额外探测。写完后执行systemctl daemon-reload && systemctl enable --now gitea,开机自启和启动一次就都完成了。如果走 Docker,等价的命令是把宿主机的 2222 映射到容器的 22 来避开系统自带 SSH。无论哪种方案,公网环境不要裸奔默认端口,尽量换高位端口并做防火墙白名单。2.4 首次安装向导:数据库和管理员一起定下来服务起来之后,浏览器打开访问地址进入安装向导。这一步要填的东西和app.ini对应,关键是两处选择。第一,数据库类型。小规模直接选 SQLite 最省事,零外部依赖,备份就是把文件拷走;预计会有多人高并发或者以后要横向扩展,就选 PostgreSQL。MySQL 也能用,但我个人体感 Gitea 对 PostgreSQL 的兼容性更稳。第二,管理员账号。虽然 Gitea 允许先不建管理员、注册后第一个用户自动成为管理员,但这样在多人同时注册时容易出现权限混乱。我习惯在安装页直接勾选管理员账号并填好用户名,一次性把管理员定下来。安装完成后,建议立刻去“站点管理 -> 配置”里核对ROOT_URL和实际对外地址是否一致,再顺手把“需要注册邮件确认”打开。这一步很多人会忽略,等仓库里放了敏感代码才后悔。3. 仓库实操:从创建到日常推送的高频路径3.1 创建仓库与首次推送的完整命令链日常最频繁的操作就是建库和推送,网页上点几下就能完成。新建仓库时,Gitea 默认给一个仓库目录,README、.gitignore、许可证模板按需生成。需要特别提醒的是:许可证文件一旦生成,后续再改很麻烦,如果团队对许可证不敏感,建议先不要生成,让维护者以后按需添加。网页建好仓库后,一个典型的新项目流程是这样的:git init my-project cd my-project git add . git commit -m "initial commit" git remote add origin git@git.example.com:yourname/my-project.git git push -u origin main如果项目已经在本地开发了一阵子,没和远程关联,那git remote add origin这一步最关键。最常见的坑是本地默认分支还叫master,而 Gitea 新建仓库默认分支是main,两者不匹配时推送会出现一堆分支来回复制。我通常直接git branch -M main先改名再推,顺手把本地分支名统一成main,后面协作就不用来回切。3.2 push、pull、fetch 的分工与本地分支改名很多人会把git pull当成“同步一下”的代名词,严格说它是fetch + merge的组合。在协作场景里,本地有未提交改动时一条git pull可能直接制造冲突。我习惯的分工是:先git fetch看远程有没有新东西,再用git log HEAD..origin/main确认差距,最后根据情况选git pull --rebase或git merge。push更简单,就是把自己的提交推上去,但推之前先pull --rebase能显著减少历史分叉。这里要强调一个认知:Gitea 的职责是保证远程仓库和本地一致、提供可浏览的变更历史,它不像网盘那样自动同步未提交的改动。Git 本身就是显式同步的设计,接受这个前提,很多“怎么没自动同步”的困惑会消失。3.3 分支保护与 Pull Request:把门禁设早一点分支保护是多人协作里最值得提早设置的功能。在仓库的“分支”设置里,把main设为受保护分支,勾选“禁止直接推送”和“需要审查后才能合并”。这样所有人都只能改功能分支,通过 Pull Request 请求合并,代码审查就成了制度的一部分。PR 流程不复杂:开一个功能分支feature/xxx,推送到远程,网页上点“新建拉取请求”,选好基础分支和对比分支,填描述,指派审查人。审查人在网页里看 Diff、留评论、请求修改,最后合入。从团队实践里总结的经验是:PR 描述按“做了什么、为什么、影响范围”三段式来写,审查效率会显著提升,Gitea 网页自带的 Markdown 编辑完全够用。如果不需要严格审查,只想让特定几个人能推main,分支保护的“推送权限”可以限定到某个团队,其他人只能从 PR 合入,这个灵活度比很多大平台的组织设置理解起来更直白。3.4 Tag、Release 与 LFS:版本发布和大文件管理版本发布在 Gitea 里很简单:在仓库的“发布”页面创建一个 Release,Gitea 会自动把对应 Tag 列出来,你可以填版本说明、附上二进制包。命令行侧的操作是:git tag v1.0.0 git push origin v1.0.0常见的错误是打了 Tag 忘了推,或者 Tag 打错了想改。已经推送到远程的 Tag 要移动很麻烦,最好在本地确认无误再推。如果要用git tag -d删本地 Tag,别忘了还要git push origin :refs/tags/v1.0.0删远端。大文件管理是 Gitea 常被忽略但很实用的能力。在仓库设置里开启 LFS,它能接管超过阈值的二进制文件,比如设计稿、数据集、安装包。服务端需要在app.ini配置[server]段的 LFS 开关和[lfs]段的MAX_FILE_SIZE。我的建议是阈值设置保守一点,超过 5MB 再启用,避免把大量小二进制文件也纳入 LFS,反而增加存储负担。4. 用户、组织与权限:多人协作时的最小授权模型4.1 用户、组织、团队三个概念怎么区分刚上手的人经常把这三者混在一起。简单说:用户账号属于个人;组织可以理解为虚拟公司或项目组,仓库的归属权挂在组织名下;团队是组织内部的分组,用来批量分配仓库权限。举例来说,你的团队叫某工作室,在 Gitea 上建一个组织,仓库全部挂在组织下,然后设“前端组”“后端组”两个团队,分别给不同仓库分配读取或写入权限。个人账号只是登录身份的载体,代码归属是组织。这样最大的好处是:某个人离职了,仓库不会跟着“失联”,把成员从组织里移除就行。如果不建组织,仓库会挂在个人账号下,协作时就得逐个仓库加人,权限盘根错节。从第一天就建组织,后面维护成本低很多。4.2 权限档位对照与最小权限原则Gitea 的权限模型比 Git 原生权限要直观,核心就几个档位:权限档位能做什么适合什么人所有者管理成员、完全控制所有设置只留两三个人写入推送代码、管理非核心仓库设置日常写代码的成员读取克隆代码、查看 Issue只读协作方、围观者分配权限时我遵循一个很朴素的规则:默认所有人都只能读取,需要写代码的才给写入,所有者尽量只留两三个人。很多事故都是“为了省事”随手给了写入,结果一个误操作覆盖了同事的提交。所有者太多还有另一个隐患:所有者之间可以互相改权限,团队一旦出现分歧,权限争夺会非常难看。粗粒度权限反而适合大多数小团队——你不需要花一周研究权限矩阵,五分钟就能理清谁该干什么。4.3 外部协作者:给只读权限而不是交钥匙还有一种常见需求:给外包或合作方一个只读访问权限,但不想让他们进入组织内部。Gitea 没有公开托管平台那种专门的协作者角色,但实现方式不复杂:把对方当作独立用户加入组织,然后只给对应仓库分配读取权限。这样他能克隆代码、看 Issue,但看不到组织里其他私有仓库,也推不了代码。这一步最容易犯的错误是直接把外部协作者设为仓库所有者,等于把仓库的搬砖权也交了出去。记得核对好分配的团队权限再点保存,必要时在成员列表里复查一遍。权限这种东西,宁可先紧后松,也别先松后紧。5. Webhook 与自动化:让仓库主动通知你的部署流程5.1 Webhook 只负责通知,不负责执行Webhook 是 Gitea 和外部系统通信的主通道。原理很简单:当仓库发生指定事件(推送、PR 更新、Issue 变更、Release 发布等)时,Gitea 向后台配置的 URL 发送一个 HTTP POST 请求,请求体是包含事件类型、仓库信息、提交列表的 JSON 数据,接收端收到再执行相应逻辑。必须明确一点:Webhook 负责通知,不负责执行。很多人误以为配了 Webhook 就自动部署了,其实 Gitea 只是把事件告诉你,真正部署脚本怎么跑、跑在哪台机器,全由接收端决定。理清这个边界,排查问题时思路就清晰了。5.2 一个推送即部署的极简接收端示例下面这个例子是我给内网测试服务器配置过的方案:监听推送事件,收到请求后拉最新代码并重启服务。接收端可以用极简的 Python HTTP 服务:from http.server import HTTPServer, BaseHTTPRequestHandler import json, subprocess class Handler(BaseHTTPRequestHandler): def do_POST(self): length = int(self.headers.get('Content-Length', 0)) payload = json.loads(self.rfile.read(length) or b'{}') event = payload.get('ref', '') if event.endswith('main'): subprocess.Popen(['/bin/bash', '/opt/deploy.sh']) self.send_response(200) self.end_headers() self.wfile.write(b'ok') HTTPServer(('0.0.0.0', 9000), Handler).serve_forever()而/opt/deploy.sh大概长这样:#!/bin/bash cd /srv/my-app git pull origin main systemctl restart my-app这套东西能跑,但只能算 demo 级。有三个隐含问题要注意:一是没有校验请求确实来自 Gitea,谁能访问这个端口谁就能触发部署;二是部署脚本没有加锁,连续两次推送可能造成两个部署进程同时跑;三是没有失败重试和日志。生产环境至少要在 Gitea 后台配置一个 Secret 令牌,接收端校验请求头里的签名或秘密头。安全这件事别侥幸,内网也一样有被扫的风险。5.3 接入 CI 工具前的网络和签名排查如果想要的不是“推上去再部署”,而是一整套构建、测试、发布流水线,那就把 Webhook 指向你的 CI 工具。Gitea 的 Webhook 天然支持这个场景,你只需要把 CI 的触发地址填进去,然后在流水线里读取仓库的提交信息。Gitea 自身也有内置 Actions,兼容社区主流的 workflow 语法,在仓库里放一个.gitea/workflows/目录下的 yaml 文件就能跑。我测试过构建镜像、跑单元测试、发 Release 这些基本需求都能覆盖。如果依赖复杂插件生态,先看官方文档再决定值不值得迁移。从经验角度说,Webhook 联动的坑通常不在 Gitea 侧,而在网络和回调地址。内网环境要确认目标机器能访问 Gitea、防火墙放行了目标端口。最好先在接收端打印一段请求体,确认 POST 真的到了,再开始写业务逻辑。我见过太多人上来就写完整部署逻辑,最后发现根本没收到请求,白忙半天。6. 备份、升级与典型故障:长期维护的保命清单6.1 用 gitea dump 做一致性备份,别只复制文件夹Gitea 自带备份命令:gitea dump。它会打包仓库数据、数据库、配置文件和附件,生成一个 zip 文件。如果用 SQLite,直接停服拷贝数据目录基本也能恢复,但数据库文件在写入中被复制容易得到损坏快照。gitea dump会先做一致性处理,比手动复制稳妥得多。我建议把备份做成定时任务,每天一次全量。数据量小时压缩后也就几百 MB,完全扛得住。恢复时把 zip 解包到新机器的同一路径,再调整一下app.ini里的路径配置就能起来。注意 dump 和恢复的 Gitea 版本尽量接近,大幅跨版本之间迁移偶尔会有 schema 不兼容的情况。6.2 跨小版本升级的正确姿势Gitea 的升级比多数自托管软件省心。官方每个季度都会发布小版本,我一般这样做:备份、下载新二进制、替换旧文件、重启服务、让 Gitea 启动时自动做 schema 迁移。整个过程中最关键的动作是“先备份再动二进制”。这里要提醒一句:不要长时间不升级。数据库 schema 会随版本演进,间隔太久升级,迁移脚本要跨很多中间版本,出问题的概率成倍上升。建议至少每个季度看一眼版本,落后安全修复版本超过两个再升级,就有点危险了。6.3 高频故障清单与从外到内的排查顺序最后把我在各个项目里碰到最多的几个问题列一下:故障表现常见原因处理建议SSH 克隆提示权限拒绝公钥没配好,或本机用了错的私钥检查 Gitea 后台的公钥登记,用ssh -T git@host测试认证网页能开但克隆地址带错端口ROOT_URL 和实际访问地址不一致到站点管理修正 ROOT_URL,重启服务二进制包无法启动系统用户或数据目录权限不对确认 RUN_USER 和数据目录属主一致,用 chown 修复反向代理后页面样式丢失反向代理没设置 Host 头代理配置里加proxy_set_header Host $host;,ROOT_URL 配成对外域名推送大文件被拒LFS 未启用或单文件上限太小开启 LFS,调整[lfs] MAX_FILE_SIZE反向代理报 502后端进程没起来或端口写错检查服务状态和监听端口,确认代理 upstream 一致排查顺序我习惯从外到内:先确认服务进程活着,再确认端口监听,再检查反向代理规则,最后翻日志。Gitea 的日志默认在log/目录,出现 500 或权限问题时,日志基本会直接告诉你原因,比瞎猜高效得多。用 Gitea 这几年,我最深的体会是:工具的价值不在于功能多,而在于能不能让团队把精力留给代码本身。部署一次之后,它几乎不会再来打扰你——不弹窗推送、不逼你升级、不搞免费额度那套。把基本操作跑熟之后,你完全可以按自己的需要往里接脚本、接机器人、接流水线。如果你正打算给团队搭一套私有 Git 服务,先别纠结那些炫酷的附加功能,把这篇里的部署、权限和备份跑通,就已经解决了 90% 的日常需求。最后再分享一个小习惯:每季度固定留出半小时,看一眼 Gitea 的版本更新和备份日志,这半小时能帮你省掉很多半夜救火的麻烦。
做技术这几年,我帮团队和个人搭过至少五套代码托管服务,从最基础的裸仓库加 Git 钩子,到完整带界面的 Gitea,再到那些动辄吃掉几个 G 内存的重量级全栈方案,折腾一圈下来的结论是:如果只是想给团队内部搞一套顺手、不用花太多心思维护的 Git 服务,Gitea 基本就是最优解。这套由 Go 写的轻量级 Git 托管平台,单二进制文件就能跑起来,内存占用常年压在几百 MB 以内,却把日常开发最常用的那套能力——仓库管理、Issue、Pull Request、Webhook、Release、LFS——都覆盖得七七八八。这篇文章不打算讲高深原理,也不推销花哨功能,就老老实实把 Gitea 从安装部署、仓库操作、权限管理到自动化联动和日常维护的基本操作梳理一遍,给正在选型或者刚装完不知道从哪下手的同学一份能照着做的参考。1. 为什么我最终选了 Gitea:一台旧服务器也能跑起的 Git 服务1.1 轻量是 Gitea 最打动人的地方Gitea 本质上是 Go 语言编译出的单个可执行文件,运行时负责 HTTP 服务、SSH 代理、Git 操作调度,外部依赖只有一个小型数据库(SQLite 或者 PostgreSQL / MySQL)。和那些要把多个子服务拆开、还要附带队列和监控组件的全栈方案不同,Gitea 开箱就是一个进程加一个数据目录。我实测过:一台 1 核 2G 的云服务器,装完 Gitea 之后空闲内存大概在 300 到 500MB 之间波动,日常跑十来个仓库毫无压力。同样规模的需求放在重量级方案上,光初始化和后台任务就可能吃满内存,小机器基本戴不动。轻量带来另一个实际好处是迁移省事。我搬过几次服务器,Gitea 的迁移就三步:停服务、打包数据目录、到新机器解包。配置、仓库数据、数据库文件全在一个目录下,不需要像复杂方案那样协调多个组件的一致状态,这一点在应急恢复时非常救命。1.2 功能覆盖:小团队 80% 的场景它都够用有些人会质疑,Gitea 毕竟是轻量定位,日常够不够用?我的答案是:小型团队最常用的那套流程它全都有。代码托管部分支持 SSH 和 HTTPS 两种克隆方式,仓库可以建私有、公开或者组织内可见;协作部分有 Issue、里程碑、看板、Pull Request、代码审查评论;发布部分有 Tag、Release,还能在 Release 页面挂构建产物;自动化部分有内置 Actions、Webhook 可以联动外部系统。对于一个二三十人的团队,这些功能不仅够用,而且够顺。我见过不少团队把 Gitea 装完之后,唯一需要外接的只剩制品库和容器镜像仓库,那属于边界需求,不影响核心使用。真正的日常工作——写代码、提 PR、评审、发版——在 Gitea 里可以闭环完成。1.3 它明显不适合的场景也要说清楚当然也要说实话,Gitea 不打算和重量级平台正面竞争的那部分,恰恰是它最短的板。如果团队需要重量级的审计合规、精细到目录级别的权限控制,或者重度依赖复杂 CI 插件生态,Gitea 会吃力不少。官方内置的 Actions 还在持续完善,和成熟 CI 相比,插件丰富度、缓存机制、并发调度的能力都有差距。我自己的建议是:项目主要是内部代码管理和团队协作,Gitea 是省心之选;如果有强制合规要求,再认真评估商业方案。2. 部署篇:下载一个二进制文件,四个步骤跑起来2.1 部署方式选择:二进制包优先于容器Gitea 的部署方式无非三种:官方二进制包、Docker 镜像、系统包管理器安装。我个人的推荐顺序是:二进制包优先,Docker 第二,系统包管理器第三。二进制包最直接的优点是确定性。从官方下载站拿到对应平台的压缩包,解压出来就是gitea一个文件,放到/usr/local/bin就能执行。没有镜像层、没有容器网络抽象,出了任何问题都好排查。Docker 方式适合本来就有容器编排环境的人,但要注意数据卷的权限问题——容器里默认以git用户跑,宿主机挂载目录的属主如果不对,就会出现“目录明明有权限却写不进去”的诡异情况。系统包管理器的版本往往滞后,打包习惯也不统一,我不建议在生产环境用。无论选哪种,核心都是三步:下载二进制、准备数据目录、写配置文件。下面按最省事的二进制方式展开。2.2 app.ini 里我会手动确认的五个配置项Gitea 的配置文件在工作目录下的custom/conf/app.ini,安装包自带默认配置,但有五个地方我每次都会手动确认:配置项作用常见错误RUN_USER运行进程的系统用户用 root 跑,权限过大ROOT_URL对外服务的完整地址漏掉斜杠或端口,克隆地址跟着错HTTP_PORTHTTP 监听端口默认 3000,容易被反代忘记转发SSH_PORTSSH 对外端口默认 22,和系统 SSH 撞车DOMAIN克隆地址里的主机名填了内网 IP 又想用域名访问其中最阴的坑是ROOT_URL没写对。比如我在内网用 IP 加端口访问,ROOT_URL写成http://192.168.1.10:3000/才正确;如果前面挂了 Nginx 做 HTTPS 域名转发,这里又要改成https://git.example.com/。这个配置错了之后,网页上复制出来的克隆地址会出现端口不对或者协议不对的问题,初学者经常被绕半天。2.3 systemd 托管与开机自启的关键细节二进制方式本地运行很简单,./gitea web就能起来,但放在服务器上肯定得托管成服务。我一般写这样一个 systemd unit:[Unit] Description=Gitea After=network.target [Service] Type=simple User=git Group=git WorkingDirectory=/var/lib/gitea ExecStart=/usr/local/bin/gitea web --config /var/lib/gitea/custom/conf/app.ini Restart=always RestartSec=5 [Install] WantedBy=multi-user.target几个细节值得说。Restart=always保证进程崩了自动拉起,但配合RestartSec=5可以避免因数据库锁或配置错误导致反复重启把 CPU 打满。Type=simple是默认值,Gitea 的主进程就是服务本身,不需要额外探测。写完后执行systemctl daemon-reload && systemctl enable --now gitea,开机自启和启动一次就都完成了。如果走 Docker,等价的命令是把宿主机的 2222 映射到容器的 22 来避开系统自带 SSH。无论哪种方案,公网环境不要裸奔默认端口,尽量换高位端口并做防火墙白名单。2.4 首次安装向导:数据库和管理员一起定下来服务起来之后,浏览器打开访问地址进入安装向导。这一步要填的东西和app.ini对应,关键是两处选择。第一,数据库类型。小规模直接选 SQLite 最省事,零外部依赖,备份就是把文件拷走;预计会有多人高并发或者以后要横向扩展,就选 PostgreSQL。MySQL 也能用,但我个人体感 Gitea 对 PostgreSQL 的兼容性更稳。第二,管理员账号。虽然 Gitea 允许先不建管理员、注册后第一个用户自动成为管理员,但这样在多人同时注册时容易出现权限混乱。我习惯在安装页直接勾选管理员账号并填好用户名,一次性把管理员定下来。安装完成后,建议立刻去“站点管理 -> 配置”里核对ROOT_URL和实际对外地址是否一致,再顺手把“需要注册邮件确认”打开。这一步很多人会忽略,等仓库里放了敏感代码才后悔。3. 仓库实操:从创建到日常推送的高频路径3.1 创建仓库与首次推送的完整命令链日常最频繁的操作就是建库和推送,网页上点几下就能完成。新建仓库时,Gitea 默认给一个仓库目录,README、.gitignore、许可证模板按需生成。需要特别提醒的是:许可证文件一旦生成,后续再改很麻烦,如果团队对许可证不敏感,建议先不要生成,让维护者以后按需添加。网页建好仓库后,一个典型的新项目流程是这样的:git init my-project cd my-project git add . git commit -m "initial commit" git remote add origin git@git.example.com:yourname/my-project.git git push -u origin main如果项目已经在本地开发了一阵子,没和远程关联,那git remote add origin这一步最关键。最常见的坑是本地默认分支还叫master,而 Gitea 新建仓库默认分支是main,两者不匹配时推送会出现一堆分支来回复制。我通常直接git branch -M main先改名再推,顺手把本地分支名统一成main,后面协作就不用来回切。3.2 push、pull、fetch 的分工与本地分支改名很多人会把git pull当成“同步一下”的代名词,严格说它是fetch + merge的组合。在协作场景里,本地有未提交改动时一条git pull可能直接制造冲突。我习惯的分工是:先git fetch看远程有没有新东西,再用git log HEAD..origin/main确认差距,最后根据情况选git pull --rebase或git merge。push更简单,就是把自己的提交推上去,但推之前先pull --rebase能显著减少历史分叉。这里要强调一个认知:Gitea 的职责是保证远程仓库和本地一致、提供可浏览的变更历史,它不像网盘那样自动同步未提交的改动。Git 本身就是显式同步的设计,接受这个前提,很多“怎么没自动同步”的困惑会消失。3.3 分支保护与 Pull Request:把门禁设早一点分支保护是多人协作里最值得提早设置的功能。在仓库的“分支”设置里,把main设为受保护分支,勾选“禁止直接推送”和“需要审查后才能合并”。这样所有人都只能改功能分支,通过 Pull Request 请求合并,代码审查就成了制度的一部分。PR 流程不复杂:开一个功能分支feature/xxx,推送到远程,网页上点“新建拉取请求”,选好基础分支和对比分支,填描述,指派审查人。审查人在网页里看 Diff、留评论、请求修改,最后合入。从团队实践里总结的经验是:PR 描述按“做了什么、为什么、影响范围”三段式来写,审查效率会显著提升,Gitea 网页自带的 Markdown 编辑完全够用。如果不需要严格审查,只想让特定几个人能推main,分支保护的“推送权限”可以限定到某个团队,其他人只能从 PR 合入,这个灵活度比很多大平台的组织设置理解起来更直白。3.4 Tag、Release 与 LFS:版本发布和大文件管理版本发布在 Gitea 里很简单:在仓库的“发布”页面创建一个 Release,Gitea 会自动把对应 Tag 列出来,你可以填版本说明、附上二进制包。命令行侧的操作是:git tag v1.0.0 git push origin v1.0.0常见的错误是打了 Tag 忘了推,或者 Tag 打错了想改。已经推送到远程的 Tag 要移动很麻烦,最好在本地确认无误再推。如果要用git tag -d删本地 Tag,别忘了还要git push origin :refs/tags/v1.0.0删远端。大文件管理是 Gitea 常被忽略但很实用的能力。在仓库设置里开启 LFS,它能接管超过阈值的二进制文件,比如设计稿、数据集、安装包。服务端需要在app.ini配置[server]段的 LFS 开关和[lfs]段的MAX_FILE_SIZE。我的建议是阈值设置保守一点,超过 5MB 再启用,避免把大量小二进制文件也纳入 LFS,反而增加存储负担。4. 用户、组织与权限:多人协作时的最小授权模型4.1 用户、组织、团队三个概念怎么区分刚上手的人经常把这三者混在一起。简单说:用户账号属于个人;组织可以理解为虚拟公司或项目组,仓库的归属权挂在组织名下;团队是组织内部的分组,用来批量分配仓库权限。举例来说,你的团队叫某工作室,在 Gitea 上建一个组织,仓库全部挂在组织下,然后设“前端组”“后端组”两个团队,分别给不同仓库分配读取或写入权限。个人账号只是登录身份的载体,代码归属是组织。这样最大的好处是:某个人离职了,仓库不会跟着“失联”,把成员从组织里移除就行。如果不建组织,仓库会挂在个人账号下,协作时就得逐个仓库加人,权限盘根错节。从第一天就建组织,后面维护成本低很多。4.2 权限档位对照与最小权限原则Gitea 的权限模型比 Git 原生权限要直观,核心就几个档位:权限档位能做什么适合什么人所有者管理成员、完全控制所有设置只留两三个人写入推送代码、管理非核心仓库设置日常写代码的成员读取克隆代码、查看 Issue只读协作方、围观者分配权限时我遵循一个很朴素的规则:默认所有人都只能读取,需要写代码的才给写入,所有者尽量只留两三个人。很多事故都是“为了省事”随手给了写入,结果一个误操作覆盖了同事的提交。所有者太多还有另一个隐患:所有者之间可以互相改权限,团队一旦出现分歧,权限争夺会非常难看。粗粒度权限反而适合大多数小团队——你不需要花一周研究权限矩阵,五分钟就能理清谁该干什么。4.3 外部协作者:给只读权限而不是交钥匙还有一种常见需求:给外包或合作方一个只读访问权限,但不想让他们进入组织内部。Gitea 没有公开托管平台那种专门的协作者角色,但实现方式不复杂:把对方当作独立用户加入组织,然后只给对应仓库分配读取权限。这样他能克隆代码、看 Issue,但看不到组织里其他私有仓库,也推不了代码。这一步最容易犯的错误是直接把外部协作者设为仓库所有者,等于把仓库的搬砖权也交了出去。记得核对好分配的团队权限再点保存,必要时在成员列表里复查一遍。权限这种东西,宁可先紧后松,也别先松后紧。5. Webhook 与自动化:让仓库主动通知你的部署流程5.1 Webhook 只负责通知,不负责执行Webhook 是 Gitea 和外部系统通信的主通道。原理很简单:当仓库发生指定事件(推送、PR 更新、Issue 变更、Release 发布等)时,Gitea 向后台配置的 URL 发送一个 HTTP POST 请求,请求体是包含事件类型、仓库信息、提交列表的 JSON 数据,接收端收到再执行相应逻辑。必须明确一点:Webhook 负责通知,不负责执行。很多人误以为配了 Webhook 就自动部署了,其实 Gitea 只是把事件告诉你,真正部署脚本怎么跑、跑在哪台机器,全由接收端决定。理清这个边界,排查问题时思路就清晰了。5.2 一个推送即部署的极简接收端示例下面这个例子是我给内网测试服务器配置过的方案:监听推送事件,收到请求后拉最新代码并重启服务。接收端可以用极简的 Python HTTP 服务:from http.server import HTTPServer, BaseHTTPRequestHandler import json, subprocess class Handler(BaseHTTPRequestHandler): def do_POST(self): length = int(self.headers.get('Content-Length', 0)) payload = json.loads(self.rfile.read(length) or b'{}') event = payload.get('ref', '') if event.endswith('main'): subprocess.Popen(['/bin/bash', '/opt/deploy.sh']) self.send_response(200) self.end_headers() self.wfile.write(b'ok') HTTPServer(('0.0.0.0', 9000), Handler).serve_forever()而/opt/deploy.sh大概长这样:#!/bin/bash cd /srv/my-app git pull origin main systemctl restart my-app这套东西能跑,但只能算 demo 级。有三个隐含问题要注意:一是没有校验请求确实来自 Gitea,谁能访问这个端口谁就能触发部署;二是部署脚本没有加锁,连续两次推送可能造成两个部署进程同时跑;三是没有失败重试和日志。生产环境至少要在 Gitea 后台配置一个 Secret 令牌,接收端校验请求头里的签名或秘密头。安全这件事别侥幸,内网也一样有被扫的风险。5.3 接入 CI 工具前的网络和签名排查如果想要的不是“推上去再部署”,而是一整套构建、测试、发布流水线,那就把 Webhook 指向你的 CI 工具。Gitea 的 Webhook 天然支持这个场景,你只需要把 CI 的触发地址填进去,然后在流水线里读取仓库的提交信息。Gitea 自身也有内置 Actions,兼容社区主流的 workflow 语法,在仓库里放一个.gitea/workflows/目录下的 yaml 文件就能跑。我测试过构建镜像、跑单元测试、发 Release 这些基本需求都能覆盖。如果依赖复杂插件生态,先看官方文档再决定值不值得迁移。从经验角度说,Webhook 联动的坑通常不在 Gitea 侧,而在网络和回调地址。内网环境要确认目标机器能访问 Gitea、防火墙放行了目标端口。最好先在接收端打印一段请求体,确认 POST 真的到了,再开始写业务逻辑。我见过太多人上来就写完整部署逻辑,最后发现根本没收到请求,白忙半天。6. 备份、升级与典型故障:长期维护的保命清单6.1 用 gitea dump 做一致性备份,别只复制文件夹Gitea 自带备份命令:gitea dump。它会打包仓库数据、数据库、配置文件和附件,生成一个 zip 文件。如果用 SQLite,直接停服拷贝数据目录基本也能恢复,但数据库文件在写入中被复制容易得到损坏快照。gitea dump会先做一致性处理,比手动复制稳妥得多。我建议把备份做成定时任务,每天一次全量。数据量小时压缩后也就几百 MB,完全扛得住。恢复时把 zip 解包到新机器的同一路径,再调整一下app.ini里的路径配置就能起来。注意 dump 和恢复的 Gitea 版本尽量接近,大幅跨版本之间迁移偶尔会有 schema 不兼容的情况。6.2 跨小版本升级的正确姿势Gitea 的升级比多数自托管软件省心。官方每个季度都会发布小版本,我一般这样做:备份、下载新二进制、替换旧文件、重启服务、让 Gitea 启动时自动做 schema 迁移。整个过程中最关键的动作是“先备份再动二进制”。这里要提醒一句:不要长时间不升级。数据库 schema 会随版本演进,间隔太久升级,迁移脚本要跨很多中间版本,出问题的概率成倍上升。建议至少每个季度看一眼版本,落后安全修复版本超过两个再升级,就有点危险了。6.3 高频故障清单与从外到内的排查顺序最后把我在各个项目里碰到最多的几个问题列一下:故障表现常见原因处理建议SSH 克隆提示权限拒绝公钥没配好,或本机用了错的私钥检查 Gitea 后台的公钥登记,用ssh -T git@host测试认证网页能开但克隆地址带错端口ROOT_URL 和实际访问地址不一致到站点管理修正 ROOT_URL,重启服务二进制包无法启动系统用户或数据目录权限不对确认 RUN_USER 和数据目录属主一致,用 chown 修复反向代理后页面样式丢失反向代理没设置 Host 头代理配置里加proxy_set_header Host $host;,ROOT_URL 配成对外域名推送大文件被拒LFS 未启用或单文件上限太小开启 LFS,调整[lfs] MAX_FILE_SIZE反向代理报 502后端进程没起来或端口写错检查服务状态和监听端口,确认代理 upstream 一致排查顺序我习惯从外到内:先确认服务进程活着,再确认端口监听,再检查反向代理规则,最后翻日志。Gitea 的日志默认在log/目录,出现 500 或权限问题时,日志基本会直接告诉你原因,比瞎猜高效得多。用 Gitea 这几年,我最深的体会是:工具的价值不在于功能多,而在于能不能让团队把精力留给代码本身。部署一次之后,它几乎不会再来打扰你——不弹窗推送、不逼你升级、不搞免费额度那套。把基本操作跑熟之后,你完全可以按自己的需要往里接脚本、接机器人、接流水线。如果你正打算给团队搭一套私有 Git 服务,先别纠结那些炫酷的附加功能,把这篇里的部署、权限和备份跑通,就已经解决了 90% 的日常需求。最后再分享一个小习惯:每季度固定留出半小时,看一眼 Gitea 的版本更新和备份日志,这半小时能帮你省掉很多半夜救火的麻烦。