
1. 方案选型为什么是 Gitea为什么用 Docker 部署聊代码仓库自托管很多人第一反应是 GitLab。GitLab 功能确实全但对企业或个人来说它那套体系的资源占用是实实在在的负担——尤其是默认配置下一台 2G 内存的小机器跑 GitLab 能直接卡到怀疑人生。相比之下Gitea 就是那个轻到不像话的替代品Go 语言写的单二进制文件官方最低配置 1 核 1G 就能跑得很欢快典型场景下内存占用大概在几百兆级别。我用一台 2C4G 的云主机跑了半年多内存稳定在 700M 左右这放在 GitLab 上几乎是不可想象的。再说 Docker 部署这件事本身。你说我也能在宿主机上直接装 Gitea 二进制为什么非要套一层容器我把实际体会说给你听一是隔离性Gitea 的配置、数据、依赖全部收在容器和挂载卷里系统升级也好、换机迁移也好直接打包带走不用跟系统环境纠缠二是版本管理Gitea 迭代频率不低容器化之后升级就是一个docker pull 重启的事回滚也简单三是依赖干净Gitea 官方容器镜像自带底层运行环境你不需要去折腾 Go runtime、node、git 这些系统级依赖也不容易出现这台机器上能跑换一台就崩的玄学问题。当然Docker 不是没有代价。多一层抽象就多一层网络和权限的坑这点后面我会专门讲。但综合来看对于个人开发者、小团队、NAS 用户这些场景Docker Gitea 的组合确实是最平衡的方案——入门门槛低维护成本小日常完全够用。这篇文章就是按我实际部署的完整路径写的适合对 Docker 有基础概念、想自己搭一个私有代码仓库的人参考。不管你用的是 Ubuntu、CentOS 还是 Debian思路基本都是通用的。在动手之前我心里有一个明确的部署预期这里先交代清楚后面所有操作都围绕它展开使用 Docker 和 docker-compose 进行编排单机部署数据目录统一放在宿主机/srv/gitea下方便备份和迁移Gitea 容器内 Web 端口为 3000宿主机映射为 3000 端口对外访问容器内 SSH 端口为 22但为了不影响宿主机自身 SSH将宿主机 2222 端口映射到容器内 22 端口数据库先用 SQLite —— Gitea 官方对 SQLite 的支持非常成熟个人和小团队使用完全没问题省掉单独维护一个数据库容器的负担2. 动手前的准备工作2.1 Docker 环境自检清单很多安装失败不是 Gitea 的问题而是宿主机 Docker 环境本身就不健康。我一贯的建议是在拉镜像之前先花两分钟做一轮检查省得后面出了问题到处排查。先确认 Docker 是否安装并启动docker version docker infodocker version会分别显示 Client 和 Server 的信息。如果 Server 部分能正常输出说明 Docker 守护进程在运行如果只显示 Client 而 Server 部分报错那基本就是守护进程没起来或者当前用户没有权限访问 Docker API。补充一句权限的问题——我见过太多人在这上面栽跟头。如果你执行docker ps时看到这样的报错permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock那说明你的用户不在docker用户组里。解决办法sudo usermod -aG docker $USER然后重新登录一次终端让用户组生效。这里有个细节只是重新打开终端窗口是不够的必须完全退出登录或者执行newgrp docker让当前会话刷新权限。如果你用的是云主机干脆直接重新 SSH 登录一次最省事。再检查一下 docker-compose 是否可用。现在 Docker Compose 已经集成到 Docker CLI 里新版本直接用docker compose version如果提示命令不存在就单独安装 compose 插件或者直接用docker run单容器方式部署后面我会给两种方式的完整命令。我个人强烈建议用 compose 方式因为把端口映射、数据卷、重启策略这些配置固化在一个 YAML 文件里后面改配置、迁移、升级都清晰得多。2.2 端口怎么选才不打架端口规划是新手最容易忽略、老手也偶尔翻车的地方。Gitea 涉及两个核心端口HTTP/HTTPS 的 Web 访问端口和 SSH 的代码拉取端口。Gitea 容器内部默认 HTTP 监听 3000SSH 监听 22。但宿主机上 22 端口大概率已经被系统 SSH 占用了直接把容器的 22 映射到宿主 22 会冲突。所以常规做法是Web 端口宿主机 3000 映射到容器 3000访问地址是http://服务器IP:3000SSH 端口宿主机 2222 映射到容器 22仓库的 SSH 克隆地址会变成ssh://git服务器IP:2222/用户名/仓库名.git这个 SSH 端口的映射逻辑要特别说清楚容器内的 git 用户 SSH 监听的是 2222容器内实际是22端口所以你在 Gitea 网页上看到的 SSH 克隆地址默认会带端口号。如果你不想每次克隆都带一个怪异的端口或者公司防火墙只放行标准端口可以在后续用 Nginx 反代做端口转发但这个不在本次部署的必要范围内。端口规划这里有一个值得提前想清楚的点3000 这个端口直接暴露在公网日志里其实很容易被扫描如果你有域名且希望对外提供服务更推荐用 80/443 端口做反向代理。但如果你只是内网使用3000 足够了没必要给 Nginx 增加复杂度。我自己的环境就是内网直连 3000图个清静。2.3 数据目录规划容器最大的特点就是无状态——容器删了里面写的东西就没了。所以必须把数据目录挂载到宿主机上。Gitea 官方镜像建议的挂载点是/data里面包含配置、仓库数据、数据库文件、日志等。我习惯将宿主机目录规划成/srv/gitea/ ├── data/ # 挂载到容器的 /dataGitea 所有数据都在这里 ├── compose.yml # docker-compose 配置文件 └── backups/ # 备份目录可选为什么放在/srv而不是/home或/root纯属个人习惯加一点 Linux 惯例——/srv就是给存放服务数据的目录用的语义清晰而且find备份脚本的时候不容易跟用户文件混到一起。你完全可以按自己的习惯选目录只要记住这个目录要够大因为以后所有 Git 仓库的裸仓库数据都会累积在这里权限要清晰通常建议属主设为当前用户或专门的服务用户。配额方面给个参考值一个人开发或三五人小团队仓库不塞大体积二进制文件的话一两年也就几个 G 的空间不用太焦虑。但如果你们团队习惯把发布包、镜像文件直接塞进 Git 历史那就要提前盘一下磁盘空间Git 历史里的东西一旦提交上去就很难真正清除。3. 两种部署方式照着抄就完事3.1 快速体验版一条 docker run 搞定如果你想最快速度看到 Gitea 跑起来的界面不用写任何配置文件直接执行下面这条命令docker run -d \ --namegitea \ -p 3000:3000 \ -p 2222:22 \ -v /srv/gitea/data:/data \ --restartalways \ gitea/gitea:latest逐行解释一下这些参数理解了之后你就不怕命令里的东西是黑盒了-d后台运行容器--namegitea给容器起个名字方便后续docker logs gitea、docker stop gitea操作-p 3000:3000将宿主机 3000 端口映射到容器的 3000 端口这是 Web 界面地址-p 2222:22将宿主机 2222 端口映射到容器的 22 端口这是 SSH 克隆地址用的端口-v /srv/gitea/data:/data数据卷挂载宿主机上的数据目录对应容器内的数据目录--restartalwaysDocker 服务启动时自动拉起容器机器重启后 Gitea 自动恢复gitea/gitea:latest官方镜像latest 标签是当前稳定版这里我额外说明一下--restartalways的意义。很多人部署完觉得容器能跑就行结果服务器一重启发现服务没了还得手动docker start一遍。这是个非常影响使用体验的细节。always策略意味着只要 Docker 守护进程是活的它就会尽力维持这个容器的运行状态。对于个人服务器这是最推荐的重启策略别犹豫。镜像拉取的过程可能会有点慢特别是官方 Docker Hub 在某些网络环境下经常超时。这个问题我在常见问题章节会专门给出处理思路。镜像拉完后访问http://服务器IP:3000页面能打开就算第一阶段成功了。3.2 规范性部署docker-compose 编排方案单条docker run适合临时体验但如果你准备长期使用我更推荐用 docker-compose 把配置固化下来。好处是显而易见的配置版本化、可追溯、改端口改路径不用记一长串命令团队成员接手也一目了然。在/srv/gitea/目录下创建compose.yml文件version: 3 services: gitea: image: gitea/gitea:latest container_name: gitea environment: - USER_UID1000 - USER_GID1000 restart: always ports: - 3000:3000 - 2222:22 volumes: - /srv/gitea/data:/data这里有两个环境变量要解释USER_UID和USER_GID。Gitea 容器内部默认会创建名为git的用户这个用户是运行 Gitea 进程并拥有/data下所有文件写入权限的主体。问题在于容器内的 UID 和宿主机是不同的体系——容器里默认的 UID 可能是 1000而宿主机上 1000 可能对应另一个用户。如果不显式指定宿主机/srv/gitea/data下生成的文件属主很可能是一个你根本不认识的 UID后面在宿主机上直接操作这些文件比如备份、迁移时就会遭遇各种权限摩擦。我的建议是将USER_UID和USER_GID设置成宿主机当前管理员的 UID/GID。查看当前用户的 UID/GID 用这个命令id输出大概是uid1000(yourname) gid1000(yourname) groups1000(yourname)这样的格式把对应的数字填进 compose 文件里就行。这样容器里生成的仓库数据在宿主机上看到的属主就是你后续任何文件操作都顺畅。配置文件写好之后启动docker compose up -d查看运行状态和日志docker compose ps docker logs -f giteadocker logs这个命令值得养成习惯。容器启动失败时日志几乎是唯一的排查入口比如数据库初始化错误、端口冲突、卷权限问题都会在日志里留下明确的报错信息。后面排查问题我会反复提到它。3.3 初始化安装完成你的第一个 Gitea 实例无论用哪种方式启动了容器第一次打开 Web 页面都会进入 Gitea 的安装配置页。很多人到这里反而容易卡住因为页面信息也不少。我按实际填写顺序帮大家过一遍关键项。数据库设置默认是 SQLite数据库文件会存放在/data/gitea/gitea.db。如果你没有单独部署 MySQL/PostgreSQL 的刚需就保持 SQLite 不动。如果你的团队规模到了几十人、并发量上来了或者你就是想在架构上把数据库拆出来那需要先在 compose 里加一个数据库服务并在 Gitea 安装页选择对应的数据库类型填入连接信息。这个属于进阶玩法本文不做展开但我会在后面简要提一下迁移思路。站点基本配置站点名称、仓库根路径这些保持默认即可。重点说一下SSH 服务器端口这一项——必须填 2222不是 22。因为容器内 SSH 监听的是 22但你在宿主机外部访问时用的是 2222Gitea 需要知道这个对外暴露的端口才会在仓库的 SSH 克隆地址里正确拼上端口号。如果你这里填错了后面复制出来的克隆地址是错的非常隐蔽的坑。管理员账号在安装页面底部创建一个管理员账号。这里我建议密码设置得稍微讲究一点因为这是你服务器上代码库的总钥匙。拿个小本本记好验证邮箱如果不想配置 SMTP 可以先留空跳过——个人使用完全不影响后续要配邮件通知再补。填完所有项点立即安装等几秒就会跳转到登录页。到这一步Gitea 本体就已经跑起来了。4. 日常使用配置与体验优化4.1 创建仓库和 SSH 密钥对接Gitea 装好之后第一个动作当然是建仓。新建仓库的流程和 GitHub/Gitee 基本一致填个名称、选公开或者私有、决定是否初始化 README没有需要特别解释的。真正要花点心思的是 SSH 密钥的配置。你在网页上设置 - SSH 密钥里粘贴公钥之前先在本地生成密钥ssh-keygen -t ed25519 -C your_emailexample.com这里推荐 ed25519 而不是传统的 RSA 2048/4096原因是 ed25519 密钥更短、生成更快、安全性也不输 RSA而且主流的 Git 和 SSH 客户端都已经原生支持。你如果还停留在用 RSA 的习惯里刚好借着这次部署升级一下。把公钥粘贴进 Gitea 后克隆仓库时就用 SSH 地址类似git clone ssh://git192.168.1.100:2222/username/repo.git注意这个地址的结构git是 Gitea 默认的 SSH 用户192.168.1.100换成你的服务器 IP2222是映射出来的 SSH 端口后面跟用户名和仓库名。这个格式跟 GitHub 的gitgithub.com:user/repo.git风格有点区别因为端口号不是默认的 22而 Gitea 对非标准端口就是这么展示的不用奇怪。4.2 数据备份删库跑路前最后一道防线自托管服务最怕的是什么数据没了。Docker 可以随处重建但 Git 仓库里的历史是重建不了的。Gitea 的数据都在/srv/gitea/data下备份方案可以很简单——把整个 data 目录打一个压缩包tar -czf gitea-backup-$(date %Y%m%d).tar.gz -C /srv/gitea data如果你希望备份过程更保险防止备份期间有写入导致数据不一致可以先停容器再打包备份完成再启动。对个人使用来说停几秒服务完全没影响。也可以不停容器直接打包绝大多数场景下不会出问题但在极端情况下文件一致性不能百分百保证。我的习惯是深夜定时任务里先docker stop gitea打包再docker start gitea既简单又稳妥。备份的留存策略也顺便说一下至少保留最近 7 天的每日备份加上每月一份归档留作跨月保存。Git 仓库是增量累积的除非有人强推或清理历史旧备份一般不会需要回溯太远但多留一份又不要花钱别偷懒。4.3 升级 Gitea 的正确姿势Gitea 官方发版比较勤安全修复和功能更新都不少升级别积压太久。Docker 部署的升级流程是我最喜欢的部分——干净利落docker compose pull gitea docker compose up -d如果你用的是docker run方式升级就是docker stop gitea docker rm gitea docker pull gitea/gitea:latest docker run -d --namegitea ... 参数同之前因为数据都在挂载卷里容器删了重建数据毫发无损。这就是容器化最直观的红利。但升级前有一个必须做的动作看一眼升级说明。Gitea 偶尔会有破坏性变更比如配置项改名、数据库结构迁移等。虽然官方镜像会自动跑迁移但万一升级后发现问题你要能快速回滚到旧版本。回滚的操作就是重新docker pull旧版本镜像标签再用同一套数据目录启动。只要数据目录没动回滚基本无损。5. 常见问题与排查技巧实录5.1 镜像拉取慢或直接超时国内网络环境下docker pull 官方镜像大概率会遇到速度问题这是很多新手的第一道坎。思路很明确给 Docker 配置镜像加速器。编辑不存在则创建/etc/docker/daemon.json{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com ] }然后重启 Dockersudo systemctl restart docker加速器的可选列表网上随时会变没有哪个是一劳永逸的我的建议是选一两个当下可用、口碑正常的镜像加速地址填进去能拉动就行。如果哪天又开始超时换一批再试。注意加了加速器只对 pull 公共镜像有加速效果你后续往自己的仓库推送镜像不经过它不受影响。5.2 容器启动失败权限、端口、日志三板斧容器启动失败的场景我总结了三个高频原因排查顺序也可以按这个来第一种是权限问题主要表现为容器日志里出现Permission denied或mkdir: cannot create directory之类的报错。多半是/srv/gitea/data目录的属主和容器内git用户的 UID 对不上。解决办法确认 compose 里的USER_UID是否为当前宿主机用户 UID或直接chown -R 1000:1000 /srv/gitea/data把属主强行改过来。第二种是端口冲突表现为日志里出现address already in use。用ss -lntp | grep 3000或lsof -i:3000查一下谁占用了端口改个映射宿主端口就行。第三种是环境变量或配置问题。比如 compose 文件里写了数据库连接信息但对应的数据库服务还没起来日志会明确告诉你是连接被拒。排查命令记这五条90% 的问题都能定位docker compose ps # 容器状态 docker logs -f gitea # 实时日志 docker inspect gitea # 容器详情包括挂载、端口映射 ss -lntp # 端口监听状态 ls -l /srv/gitea/data # 数据目录属主和权限5.3 SSH 克隆提示权限被拒绝仓库建好了SSH 公钥也加了但克隆时提示Permission denied (publickey)。这个问题的排查思路比问题本身更重要。先明确一个概念Gitea 的 SSH 认证最终是验证你提供的公钥是否跟仓库所属用户关联的公钥匹配。常见的失败原因有添加公钥时把私钥或公钥内容粘贴错了——私钥是-----BEGIN OPENSSH PRIVATE KEY-----开头的那一大段公钥是ssh-ed25519 AAA...结尾带邮箱的那一行。粘贴的时候只贴公钥。克隆地址里的端口写错了。如果你在网页上复制的克隆地址是带 2222 的但手动改成 22就会连到宿主机的系统 SSH 上去那自然是认证失败的。本机使用 SSH 代理比如 ssh-agent 里加载了多个密钥而 git 连接时没有使用正确的那一把。此时可以在~/.ssh/config里给这台服务器单独指定 IdentityFile。还有一种让人抓狂的情况网页上地址是对的本地命令也没问题但容器里 Gitea 的 SSH 压根没启用。进容器检查一下docker exec -it gitea sh ps aux | grep ssh正常应该能看到 sshd 的进程。如果看不到多半是 Gitea 配置里 SSH 功能被关掉了或者是端口没有正确映射。这种时候最快的方式就是在安装配置文件app.ini里检查[server]段的配置项确保START_SSH_SERVER true。配置文件路径在容器内是/data/gitea/conf/app.ini在宿主机上就是/srv/gitea/data/gitea/conf/app.ini——改完配置文件要重启容器才生效。5.4 SQLite 与外部数据库的权衡最后聊一下这个经常被问到的话题个人用SQLite 到底够不够我的答案是在大多数场景下完全够。Gitea 针对 SQLite 做了大量优化甚至官方文档里都提到小规模部署推荐 SQLite它在读写性能上的表现对几十人以下的团队没有明显瓶颈。SQLite 最大的优点就是零维护不需要单独管理数据库进程备份就是拷一个文件迁移就是把文件带走。什么时候需要迁移到 MySQL/PostgreSQL当你感觉到数据库成了瓶颈——比如仓库数量上千、并发操作频繁、或者你希望数据库能独立扩展和备份那就可以考虑引入外部数据库。迁移的方式是先重启 Gitea 维护模式用gitea dump工具备份数据然后在app.ini里修改数据库连接配置再导入。这是一整套流程篇幅所限不展开。我的态度是用上 SQLite 别焦虑真到了需要迁移的时候Gitea 官方工具链已经把这些都铺平了。6. 写在最后的一点实践经验这套 Gitea Docker 的组合我在不同环境下部署过好几轮——从云主机、物理机到家用 NAS踩过的坑基本都写在上面了。如果让我只总结一条最重要的经验那就是把一切配置都在 compose 文件和数据目录里固化下来不要用一次性命令去管服务。配置文件是最好的文档也是最快的复原路径。你半年后再回来看这套部署只要读一遍 compose 文件不用回忆一切清清楚楚。还有一个我特别推荐养成的习惯每次改完配置、升完级、做完备份顺手把当时的操作过程和最终状态记到项目的一个 Markdown 文件里。这个文件对运维的价值甚至超过任何自动化工具——尤其是当你在多个服务器上重复部署时它就是你自己的最佳实践手册。Gitea 自己就是管文档的地方把这个说明文档也放进仓库也算是对这套自托管方案的最好注解。