玩自托管这几年,我断断续续在好几台设备上折腾过代码仓库。GitLab 太重,Gitea 官方 Docker 镜像又轻又稳,配合 Synology 的 Docker 套件,十几分钟就能把私人 Git 服务跑起来。这篇就把我从零开始的操作过程、踩过的坑、以及后续运维经常要用到的细节一并写出来,给想在 NAS 上搭代码仓库的朋友做个参考。整个过程不复杂,只要跟着步骤走,基本不会卡壳。
1. 方案选型:为什么是 Gitea 加 Docker
1.1 Gitea 到底解决了什么问题
个人开发者、小团队或者极客玩家,对代码托管的需求其实很明确:要有 Git 的完整功能,要能管理多个仓库和用户权限,要能看提交记录和 Issue,最好还带一个简单的 Wiki。Gitea 就是用 Go 写的这么一套轻量级 Git 服务,单体二进制文件就能跑,资源占用比 GitLab 低一个量级。
我自己实测过同样的 NAS 设备,GitLab 的容器跑起来内存经常飙到 2GB 以上,Gitea 加上配套的数据库,内存占用通常能控制在 300MB 到 500MB 之间。对于 Synology 这种内存本来就不宽裕的 NAS 来说,这个差距是决定性的。而且 Gitea 的界面非常接近 GitHub 的操作逻辑,从 GitHub 迁过来的人几乎不需要学习成本,创建仓库、发起 Pull Request、管理 Issue 这些东西都是一眼就能找到的。
再往深一层说,自托管 Git 服务的核心价值不只是“代码放在自己手里”,更在于它可以和现有工作流无缝集成。Gitea 支持 Webhook,可以勾连 CI/CD 工具;支持 Git LFS,可以管理大文件;支持镜像仓库功能,可以把 GitHub 上的公开仓库定时同步一份到本地,作为代码资产备份。这些能力对于一个小团队或者个人开发者来说,足够覆盖绝大部分日常场景。
1.2 为什么用 Docker 而不是套件中心
Synology 套件中心确实有 Git 相关的套件,但功能普遍比较基础,而且版本更新往往滞后。Gitea 的迭代速度很快,安全补丁和功能更新频繁,如果用套件方式安装,等官方推送新版本可能要很久,也缺少灵活的配置项。
Docker 方案最大的优势就是环境隔离和可移植性。Gitea 运行在容器里,依赖的运行时环境、配置文件、数据目录全部由镜像统一管理,不会污染 NAS 本体的系统。以后想升级,直接拉新镜像重建容器就行;想迁移到别的机器,把数据目录打包带走,到新机器重新跑一个容器挂载同样的目录,数据立刻恢复。
另外,Docker 的端口映射非常灵活。Synology 的 80 端口通常被 Web Station 占用,22 端口被系统 SSH 占用,如果你直接在 NAS 上装 Gitea,很可能遇到端口冲突。但通过 Docker 的端口映射,可以把容器内部的 3000 端口映射到宿主机任意一个空闲端口,比如 3000 或者 8888,SSH 端口也可以从 22 映射成 2222。这种“容器内不动,宿主机随意映射”的机制,让部署变得极其干净。
1.3 我在方案选择上的几个取舍
部署之前,有几个方向性的问题需要先想清楚。第一个是数据库的选择,Gitea 支持 SQLite、MySQL、PostgreSQL。个人使用或者几个人的小团队,直接用 SQLite 最省事,一个文件就把所有数据存了,备份就是复制文件。用户量上来或者有高并发需求,再换 PostgreSQL 也不迟,Gitea 官方后台有数据迁移工具,虽然不能自动化迁移,但操作流程是有文档支撑的。
第二个是是否配置 SSH 访问。Gitea 的 HTTP 克隆地址也能用,但 SSH 方式免密推送更方便,特别是对于经常提交代码的人来说,每次输密码非常影响效率。Gitea 容器支持开启内置 SSH 服务,只需要把宿主机的某个端口映射到容器内的 22 端口即可。
第三个是反向代理要不要配。如果只是局域网内访问,NAS 的 IP 加端口号直接用就行;如果想通过域名访问,域名解析到 NAS 的 IP,再把 80/443 端口的请求反代到 Gitea 容器的 3000 端口。Synology 自带的反向代理功能就可以搞定,不需要额外装 Nginx。
2. 部署前的准备:版本要求与关键概念
2.1 硬件与系统版本的最低门槛
理论上,只要是能装 Docker 套件的 Synology 机型都可以跑 Gitea。DSM 6.2 以上版本都内置了 Docker 套件,DSM 7.0 之后 Docker 改名为 Container Manager,功能上区别不大。我这里以 DSM 7 的 Container Manager 为例做说明,DSM 6 的操作路径基本一致,只是套件名字叫 Docker。
硬件方面,Gitea 的官方镜像对 CPU 的要求不高,x86 架构的主流型号都没问题。如果你用的是 ARM 架构的机型,比如老款的 DS218play 或者部分 J 系列,拉镜像时需要留意选择对应 ARM64 的版本标签。Gitea 官方镜像默认支持多架构,直接拉取时会自动匹配,但如果手动指定了 amd64 的标签,在 ARM 机器上是跑不起来的。
另外建议内存至少 1GB 以上。我最初在一台 512MB 内存的老 NAS 上试过,Gitea 本身能启动,但一旦同时跑数据库和其他容器,系统就会变得非常卡顿,Git 操作也会出现明显延迟。加内存到 2GB 后,整个体验才算是可用的状态。
2.2 Docker 的几个核心概念怎么理解
不管你是在 Synology 上还是在其他平台上用 Docker,有几个概念必须弄清楚,否则配置的时候容易一头雾水。
镜像可以理解成一个只读的模板,里面包含了应用运行所需的代码、运行时、依赖库和默认配置。容器是镜像运行起来后的实例,可以启动、停止、删除,容器内的文件系统是可写的,但容器删除后这些改动就没了。卷则是把容器内的某个目录映射到宿主机的一个目录,这样即使容器被删掉重建,数据依然保存在宿主机上,不会丢失。
端口映射解决的是“容器外面怎么访问容器里面”的问题。Gitea 容器内部监听 3000 端口,宿主机通过某个端口把请求转发到容器的 3000 端口,比如访问http://NAS的IP:3000就能打开 Gitea 界面。SSH 同理,把宿主机的 2222 端口映射到容器内的 22 端口,用户就可以用ssh://git@NAS的IP:2222/用户名/仓库名.git来克隆和推送代码。
docker run命令是把镜像启动为容器并配置各种参数的入口,在 Synology 的图形界面里,这些参数被拆分成各个选项卡和表单。理解了底层命令的逻辑,图形界面里的每一项配置就能对号入座了。
2.3 网络模式与端口规划怎么做
Container Manager 新建容器时,网络设置里有 bridge 和 host 两种常见模式。bridge 模式是默认推荐的方式,容器有自己的虚拟 IP,宿主机通过端口映射把流量转发进去。host 模式直接让容器共享宿主机的网络栈,不需要端口映射,但会直接占用宿主机端口,灵活性差一些。Gitea 用 bridge 模式就足够了。
端口规划上,我建议 Web 端口和 SSH 端口各占一个。Web 端口选一个不容易被其他服务占用的端口,比如 3000 或者 8888。SSH 端口建议选 2222,避免和宿主机本身的 22 端口冲突。这里有一个常见的坑:如果你把容器 SSH 端口映射到 22,而 NAS 的系统 SSH 也监听在 22,两者会冲突,导致其中一个起不来。所以要么改系统 SSH 端口,要么容器 SSH 就用 2222,后者显然更省事。
另外提一下,如果你要使用 Webhook 或者通过公网访问 Gitea,最好提前规划好“外部访问地址”。这个地址会写入 Gitea 的配置文件,直接影响克隆 URL 的生成。如果规划不当,后面再改会比较麻烦,不过这个问题我们在第 3 章的首次配置部分会详细展开。
3. 完整部署过程逐步演示
3.1 第一步:在 File Station 里建好目录结构
正式拉镜像之前,先要规划好数据存放的目录。我习惯在 Docker 的共享文件夹下按应用名建子目录,这样所有容器数据都能集中管理,备份时一目了然。
在 File Station 里进入docker共享文件夹,新建gitea文件夹,再在gitea下分别建data和config两个子文件夹。data用于存放 Git 仓库数据、数据库文件和上传的附件,config用于存放 Gitea 的配置文件。这两个目录后面要挂载到容器内。
注意:如果你打算以后用容器跑备份或者恢复,目录结构一定要从一开始就规划好,避免后面迁移时路径混乱。我的习惯是
/docker/gitea/data和/docker/gitea/config,在任何机器上看到这个路径就知道是干什么的。
3.2 第二步:拉取 Gitea 镜像
打开 Container Manager,进入“注册表”或“镜像”页面,搜索gitea/gitea。官方镜像名称就是gitea/gitea,别搜成别的,否则可能拉到第三方打包的镜像,配置路径和数据目录有差异,出了问题不容易排查。
标签选择上,如果你对稳定性要求高,选latest即可,Gitea 的 latest 指向的是当前稳定版本。如果你需要长期固定版本,可以选带版本号的标签,比如1.22.1之类的具体版本。我个人倾向于用带版本号的标签,这样容器重建时可以确保版本完全一致,等确认新版本没问题再手动升级。
点击下载后,等待镜像拉取完成。镜像大小一般在 100MB 左右,具体取决于网络环境,通常一两分钟就能拉完。
3.3 第三步:创建容器并配置核心参数
镜像拉取完成后,点击“运行”或“创建容器”,进入配置界面。这里有几个关键配置项需要仔细设置。
容器名称随意,比如gitea,方便辨认即可。勾选“启用资源限制”的话,可以给容器设定内存上限,防止 Gitea 异常时拖垮 NAS 系统,我一般设置 1024MB 上限。
端口映射部分,“本地端口”填写你希望外部访问的端口,容器端口固定为 3000(Web)和 22(SSH)。假设我规划 Web 用 3000,SSH 用 2222,那么映射关系就是3000 -> 3000和2222 -> 22。
存储空间映射部分,把本地的docker/gitea/data映射到容器内的/data,把docker/gitea/config映射到容器内的/data/gitea/conf。注意这里有个细节:Gitea 官方镜像只声明了一个数据卷/data,所有配置、仓库、数据库都会存放在/data下的子目录中。如果你像我一样单独挂载了/data/gitea/conf,配置文件会单独存放在宿主机目录里,方便直接编辑。如果不单独挂载,配置文件会嵌套在/data目录下,docker/gitea/data里面会自然生成gitea/conf结构。两种方式都可以,但挂载方式更有利于直接修改配置。
环境变量部分,首次部署可以先不用设置太多,后面配置向导会引导你填写。如果希望跳过向导直接完成配置,可以设置GITEA__server__ROOT_URL、GITEA__server__SSH_PORT等参数,但新手不建议这样做,首次配置还是可视化操作更直观。
配置完成后点击“应用”或“创建”,容器会自动启动。
3.4 第四步:浏览器打开安装向导完成初始化
容器启动后,在浏览器访问http://NAS的IP:3000,就能看到 Gitea 的安装页面。
这里有几个配置项需要注意。数据库类型,如果是个人使用,选择 SQLite;如果有一定的并发或者需要集中管理多个实例,选择 PostgreSQL 或 MySQL,需要提前准备好数据库容器。我个人的选择是 SQLite,前期部署最简单,数据备份直接复制文件就行,实际体验中即使同时有几十个用户操作也没有明显瓶颈,只有在仓库数量特别大、并发操作特别多的情况下,SQLite 才会成为瓶颈,这种情况在个人使用中很少见。
站点名称随便填,“仓库根目录”保持默认值即可,不要随意改动,因为这是容器内的路径,不是宿主机路径。SSH 服务器端口填2222,这里要和你设置的主机映射端口保持一致,因为 Gitea 会根据这个端口生成 SSH 克隆地址。还有一个关键的配置项是“Gitea 基础 URL”,这里要填用户访问 Gitea 时使用的完整地址,比如http://NAS的IP:3000或者你绑定的域名。这个地址会被记录到配置文件中,影响 Web 克隆地址的生成。如果填错了,克隆地址就会变成内网 IP 或者 localhost,别人拿过去根本用不了。
管理员账号设置部分,建议勾选“立即创建管理员账号”,设置好管理员用户名、邮箱和密码。如果跳过这一步,后续第一个注册的用户会自动成为管理员,存在潜在风险。
点击安装后,Gitea 会初始化数据库,几秒钟后自动跳转到登录页面。到这里,一个基础的 Gitea 服务就算跑起来了。
3.5 第五步:验证 SSH 克隆与推送是否正常
Web 界面创建好一个测试仓库后,首要事情就是验证 SSH 访问是否正常。在用户设置里找到“SSH 密钥”页面,添加你本机的公钥,然后在仓库页面复制 SSH 克隆地址,正常情况应该长这样:ssh://git@192.168.1.100:2222/用户名/仓库名.git。
然后在本机执行git clone这个地址,如果能够成功拉取代码,说明 SSH 配置链路完全正常。如果提示ssh: connect to host ... port 2222: Connection refused,多半是端口映射没生效,回到容器设置里检查端口映射。如果提示Permission denied (publickey),要么公钥没加进去,要么 Gitea 的 SSH 服务没有正确读取公钥数据。
Gitea 用户管理 SSH 公钥的逻辑是:公钥数据存在数据库里,当 SSH 请求进来时,Gitea 内置的 SSH 服务会从数据库里查找匹配的密钥。所以如果你是直连容器内 22 端口的 SSH 方式,必须保证容器里的用户目录和数据库是关联的,这个关联关系在官方镜像中已经处理好,只要按照默认路径映射/data卷就不会有问题。
3.6 其他值得做的优化配置
基础服务跑起来后,我还会顺手做几件事,让整个系统更稳定、更好用。
第一件事是在 Container Manager 里开启“自动重启”,这样 NAS 重启或者 Docker 服务重启后,Gitea 容器会自动恢复运行,不用手动再去启动。这个选项通常在创建容器时的“高级设置”里,也可以在容器创建后通过“编辑”入口修改。
第二件事是选择合适的镜像更新策略。我建议不要设置自动拉取最新镜像,因为 Gitea 一个版本升级后,数据库自动迁移通常是不可逆的,万一新版本有问题,回滚会比较麻烦。我通常在确认某个新版本发布后,先在测试环境里跑几天,确认没问题再手动升级生产环境的镜像。
第三件事是配置定时备份。我们放在第 5 章具体说明,这里先提一个核心思路:只要/docker/gitea目录有完整备份,整个 Gitea 服务就能随时重建,因为代码仓库、数据库、配置文件、用户上传的附件全部在这个目录里。
4. 常见问题与排查技巧实录
4.1 容器端口冲突怎么办
这是我最常遇到的问题。Synology 原生服务占用的端口比较多,比如 5000/5001 是 DSM 管理界面,80 是 Web Station,443 是 HTTPS,22 是系统 SSH。如果你规划的端口和这些冲突,容器会启动失败,Container Manager 里会直接报“端口已被占用”的错误。
解决思路很简单,换一个没有被占用的端口。但在换端口之前,可以在 Container Manager 的“网络”或者终端里执行netstat -tlnp查看端口占用情况,判断真正冲突的端口是哪个。有些服务监听的是 0.0.0.0(所有网络接口),有些只监听某个具体 IP,情况不同处理方式也不一样。
如果某个端口被系统关键服务占用,又实在想用这个端口,可以改系统服务的监听端口,但这样操作风险较高,不推荐新手尝试。我个人的经验是:Web 用 3000,SSH 用 2222,这两个端口在大多数 NAS 上都是空闲的,基本不会冲突。
4.2 Docker Desktop 启动失败和 NAS 部署有什么关系
不少人在 Windows 上折腾过 Docker Desktop,也踩过Docker Desktop failed to start because virtualization support wasn't detected这类报错。这个报错的意思是宿主机没有开启硬件虚拟化支持,或者 WSL2 所需的虚拟化平台没有启用。
这里顺便说清楚一个区别:在 Synology NAS 上跑 Docker,不依赖 Windows 的虚拟化支持,因为 NAS 本身就是 Linux 系统;而在 Windows 上跑 Docker Desktop,需要在 BIOS/UEFI 里开启 VT-x/AMD-V,并且要启用 Windows 的 Hyper-V 或 WSL2 功能。如果你是在 NAS 上部署 Gitea,遇到启动问题不太可能是虚拟化导致的,更多的还是端口、目录映射或者镜像架构的问题。如果是在 Windows 本地想跑 Docker 环境做开发测试,再检查 BIOS 虚拟化开关和 Windows 功能选项。
4.3 Gitea 的 SSH 配置了但连不上
这个问题的排查优先级,我一般按下面的顺序来:
第一,确认端口映射是否正确。进入 Container Manager 的容器设置页面,查看端口映射一栏,确认宿主机端口是否指向容器内的 22 端口。第二,确认 Gitea 配置中的 SSH 端口是否和映射的宿主机端口一致。进入站点管理后台,查看“SSH 服务器端口”字段,如果填的是 2222,那么 SSH 克隆地址里显示的端口也是 2222,必须和实际映射端口一致。第三,确认用户公钥是否添加成功。第四,确认容器日志中有没有报错。
一个容易被忽略的细节是:Gitea 的 SSH 克隆地址需要基于ROOT_URL和SSH_PORT两个配置来生成。如果在安装向导里填写的“基础 URL”是http://NAS的IP:3000,SSH 端口填的是 2222,那么克隆地址会正确显示为ssh://git@NAS的IP:2222/...。如果基础 URL 填的是 localhost,那克隆地址就会变成ssh://git@localhost:2222/...,拿这个地址在别的机器上克隆,当然连不上。
4.4 密码不对、权限错误这类登录问题
Gitea 的密码策略和系统账号体系是独立的。你在 DSM 里创建的账号,不能直接用来登录 Gitea。Gitea 的用户体系由它自己的数据库管理,和 NAS 系统账号没有任何关系。所以如果你在 Gitea 里提示密码不对,基本就是账号密码确实不对,或者你试图用 DSM 的账号密码登录 Gitea。
解决方式是使用浏览器开发者工具或 Gitea 后台管理功能来重置密码。如果你有系统管理员权限,可以在 Gitea 的后台“用户管理”页面找到该用户,编辑并重新设置密码。如果管理员账号都无法登录,可以通过数据库手动更新管理员密码的哈希值,但这种方式比较繁琐,更简单的方式是直接修改 Gitea 配置文件,临时允许外部注册,注册一个新管理员后,再关闭开放注册。
另外提醒一句:Gitea 有登录失败次数限制,多次输错密码后,该 IP 可能会被临时锁定一段时间,所以排查问题的时候不要疯狂试密码。
4.5 容器迁移和备份恢复的注意点
Gitea 容器的迁移其实非常友好,因为整个应用的状态都保存在/data目录里。迁移的步骤就是:老 NAS 上停掉容器,把/docker/gitea目录完整打包;新 NAS 上建好同样的目录结构,把包解压进去;然后创建容器,挂载同样的目录,设置同样的端口映射。启动后直接访问,所有的仓库、用户、配置都还在。
这里有一个必须注意的细节:如果新 NAS 的 IP 变了,SSH 克隆地址里面的 IP 自然也会变,但 Gitea 内部记录的仓库路径和 Clone URL 是基于app.ini中配置的ROOT_URL生成的,所以迁移后需要登录后台,把站点基础 URL 和 SSH 端口修改成新环境的值。Git 客户端本地已有的仓库 remote 地址也需要重新设置:git remote set-url origin <新地址>。
备份频率上,我自己的策略是每天凌晨做一次增量备份,每周做一次全量备份。同步工具用了 Hyper Backup,直接把/docker/gitea目录作为备份源,备份目的地是另一台 NAS 或者外置硬盘。因为你还要考虑到仓库数据可能比较大,如果备份目标设备的空间有限,就要规划好保留策略。
5. 进阶玩法与日常运维心得
5.1 用群晖反向代理绑定域名
如果你想通过域名方式访问 Gitea,而不是每次都用 IP 加端口,可以用 Synology 自带的反向代理功能。进入“控制面板”->“登录门户”->“高级”->“反向代理”,新增一条规则:来源协议选择 HTTPS,来源主机名填写你的域名,来源端口填 443;目的地协议选 HTTP,目的地主机名填 localhost 或 NAS 的 IP,目的地端口填 3000。
配置完成后,还需要在 Gitea 后台把“基础 URL”修改为https://你的域名,这样克隆地址会自动变成这个域名。如果你在 DSM 上已经申请并配置了证书,反向代理会自动处理 HTTPS 加密。有一点要特别说明:反向代理生效后,原来的http://NAS的IP:3000依然可以访问,Gitea 的用户名密码登录信息是存储在 Cookie 里的,同一个浏览器同时用两个地址访问可能会造成登录态互相干扰,有条件的话就统一用一个入口地址。
5.2 开启仓库镜像与外部备份
Gitea 自带了仓库镜像功能,可以从外部 Git 服务同步仓库。在用户设置或系统管理后台,可以添加“镜像仓库”,填上源仓库的 HTTPS 或 SSH 地址,Gitea 会定期拉取更新。这个功能非常适合做 GitHub 仓库的本地备份,尤其是那些自己参与维护的开源项目。
镜像同步默认是定时执行的,间隔时间最小单位是半小时。如果你希望一个仓库的高频备份,也可以用 Webhook 的方式,在 GitHub 端配置推送事件,Gitea 端接收后自动拉取。不过这种方式配置复杂一些,大多数人用定时镜像就足够。还有一点是关于 Gitea 的 LFS 功能,如果你用 Git LFS 存储大文件,LFS 文件默认也存放在/data下的lfs目录,备份时一并带上就行。这点容易遗漏,因为如果只备份了仓库但没备份 LFS 目录,大文件会丢失。
5.3 版本升级的稳妥路径
Gitea 的版本升级不算复杂,但有几件事必须提前做。第一步是备份数据和配置,确保万一出问题可以快速回滚。第二步是在 Container Manager 里拉取新版本镜像,比如从1.22.1升级到1.23.0。第三步是停止旧容器,注意不要删除,因为删除是物理移除,虽然数据在挂载卷里不会丢,但容器配置和网络设定会丢。第四步是用新镜像重新创建容器,端口映射和数据卷挂载参照旧容器的设置。第五步是启动新容器,观察日志确认启动正常,然后访问 Web 界面跑一遍核心功能。
Gitea 在启动新版时会自动执行数据库迁移,迁移通常是一次性的,并且会在日志里记录迁移过程。如果在迁移过程中报错,最理智的做法是立刻停掉容器,恢复备份,然后仔细看日志分析原因。我自己遇到过一次数据库迁移卡在某个表上的情况,原因是数据库版本太旧,跨了太多主版本,后来是分步升级才解决的。所以如果长时间没有升级,尽量不要跨越多个大版本直接升到最新。
5.4 我自己的一些后续扩展思路
Gitea 跑稳定之后,我还会在 NAS 上搭配一些其他服务,让整个开发环境更完整。比如配合 Drone 或 Woodpecker CI 做自动化构建,提交代码后自动跑测试和构建;配合本地 Docker Registry 做镜像私服,方便推送和拉取内部镜像;Gitea 的 Webhook 配置也很灵活,可以联动群晖的 Chat 套件或者其他消息通知服务,仓库有提交、有 Issue 更新就推送通知到手机。
这些扩展本质上都是围绕 Gitea 的 Webhook、API 和容器化特性来做的,不用一次性全上,按需添加就行。比如你的团队突然需要 CI/CD,那就先加一个 Woodpecker CI 容器,再把 Gitea 的 OAuth 应用配置好,用户就可以直接用 Gitea 账号登录 CI 系统了。迁移和扩展的过程,我自己体会下来,还是那句老话:只要数据卷在,容器随时可以重建,数据永远丢不了。这也是我在 NAS 上跑任何自托管服务时最看重的一点。