Docker部署GitLab全攻略:从安装到生产运维的完整实践指南 如果你正打算自己搭一个代码托管仓库GitLab 应该是绕不开的名字。我在服务器上折腾 GitLab 私有仓库这件事前前后后踩了少说七八个坑最早是在一台旧机器上直接用安装包硬装结果系统一升级依赖就全乱卸载也卸不干净最后忍无可忍才整体切到 Docker 部署。就这么一次迁移我算是彻底明白了只要是想长期运维、频繁升级、偶尔还要换机器迁移的 GitLab用 Docker 来安装部署是目前最省心的方式没有之一。这篇文章不打算复读官方文档而是把 Docker 环境下 GitLab 安装部署从选型、容量规划、端口设计、实际启动命令、生产级编排配置到部署完之后的邮件、SSH、HTTPS、备份升级、故障排查整条链路都再过一遍。你会看到我实际用的命令、踩过的坑以及一些常规文档里不太会告诉你但非常关键的小经验。事情都有两面Docker 解决了隔离和迁移问题但也带来了一些新的坑比如权限、日志、资源占用这些后面都会逐一展开。适合谁看如果你正打算在测试机或生产机上部署 GitLab或者你已经部署完了但遇到启动慢、权限错乱、克隆地址不对这类问题这篇文章应该能从底层逻辑上帮你想清楚问题出在哪。哪怕你是刚接触代码仓库的小白照着步骤一步步来也能在自己的服务器上把一个能用的 GitLab 跑起来。1. 为什么我坚持用 Docker 装 GitLab一次痛苦的迁移经历1.1 直接装在宿主机上爽快只是一时的最早我图省事直接在 Ubuntu 服务器上用官方仓库装的 GitLab。装起来确实快apt 拉完包一条gitlab-ctl reconfigure跑完就能用。但问题也是在用的过程中一点点冒出来的。最难受的是系统级依赖互相打架。GitLab 依赖的 Ruby、PostgreSQL、Redis 版本跟服务器上其他服务需要的版本不一致时你不能随便动系统环境一动手就是连锁反应。有一回我为了另一个工具升级了系统自带的 PostgreSQL结果 GitLab 直接起不来排查了一整天才发现是依赖被顶掉了。再有就是卸载。GitLab 的安装涉及几十个目录和服务靠包管理器卸载完还会剩下一堆残留比如git用户、gitlab-*服务、配置目录、PostgreSQL 数据文件、日志轮转配置。你手动去删又不敢删干净怕删到别的服务的东西。这种“请神容易送神难”的体验我相信很多直接装过的人都懂。1.2 Docker 到底改变了什么切到 Docker 之后最直观的感受是“万事不决删容器重来”。GitLab 的所有进程都跑在容器里宿主机只需要有一个 Docker 引擎其他什么都不用装。GitLab 所用的 PostgreSQL、Redis、Nginx这些都是容器镜像内部的组成部分和宿主机环境完全隔离开。系统升级、安装其他软件基本不再影响 GitLab。再一个是迁移。以前换服务器要备份 GitLab 数据、在新机上装版本、恢复配置、处理依赖问题折腾一下午是常事。现在我的做法很简单把整个 GitLab 相关的数据目录、配置目录、日志目录打包带走新机器上拉起同样版本的容器挂载回原路径服务照跑。当然也有代价。镜像本身比较大再加上数据卷磁盘占用明显比直接安装多出几个 G。另外容器与宿主机的端口映射、文件权限映射如果不了解原理坑也不少。这就是为什么我建议部署前先想清楚下面几个问题而不是上来就复制命令。维度宿主机直接安装Docker 部署环境隔离差依赖易冲突好进程和依赖全在容器内卸载干净度差残留多好删容器即可迁移便捷性差需要重新安装配环境好挂载目录迁移即可磁盘占用相对小相对大镜像加数据卷升级复杂度依赖系统包高风险换镜像 tag相对可控2. 部署前必须想清楚的三件事容量、目录和端口2.1 资源预算多少人用的仓库需要多大的机器GitLab 是出了名的吃内存这一点不是开玩笑。我第一次部署的时候只给了 2G 内存跑起来之后加载慢到让人怀疑人生提交代码偶尔直接 504。后来把内存升到 4G再把 swap 加上情况才好了很多。如果按实际使用场景来做资源预算我推荐按照下面的底线来准备团队规模建议内存建议 CPU建议磁盘不含备份个人/小团队1-5人4G 起2 核50G中型团队10-20人8G4 核200G较大规模50人以上16G 以上8 核根据仓库量评估注意一点这里说的内存是“代码托管 CI/CD 一起跑”的配置。如果只做代码托管不用流水线内存可以适当减半。但无论如何2G 内存跑 GitLab 都属于勉强家里测试倒还好生产千万别这么干。磁盘方面GitLab 对存储的消耗远不止仓库文件本身。Git 对象、LFS 大文件、CI 构建缓存、日志这些都在悄悄占空间。建议在规划目录时把数据目录单独放到容量大、且支持备份的磁盘上比如独立的数据盘而不是系统盘。2.2 目录挂载三个目录决定你的数据生死Docker 部署 GitLab 最关键的就是目录挂载。很多人只挂一个数据目录结果升级或容器重建之后发现配置和日志丢了或者 GitLab 又回到了初始状态。GitLab 官方推荐的挂载目录是这三个/etc/gitlab存放 GitLab 主配置文件 gitlab.rb 和 SSL 证书等/var/log/gitlab存放各种服务日志/var/opt/gitlab存放 Git 仓库数据、数据库文件、备份文件把这三个目录分别挂载到宿主机的持久化路径下比如/srv/gitlab/config、/srv/gitlab/logs、/srv/gitlab/data。这样无论容器怎么删怎么重建只要挂载关系正确数据和配置都不会丢。这里有个容易犯的错宿主机目录的权限问题。Docker 会按照镜像内指定的 UID/GID 来写这些挂载目录。GitLab 容器内部进程是以root或git用户身份运行的其中git用户的 UID 一般是 998。如果宿主机上对应目录的所有者或者权限不对容器内的 GitLab 可能没有权限读写表现出来就是各种诡异故障比如仓库无法写入、页面显示 500、备份失败等。所以我每次新建宿主机目录都会先确认目录权限设置合理再启动容器。一个简单的做法把目录交给root然后让容器内的进程去处理权限问题实在不行再手动 chown。提示在弄不清楚权限该怎么设的时候先保证目录存在且宿主机用户有读写权限然后启动容器观察日志。不要一上来就 chmod 777那不是解决问题的办法反而会引入新的安全风险。2.3 端口规划装到一半发现 80 被占了才叫尴尬GitLab 容器内部默认监听三个端口80HTTP、443HTTPS、22SSH。这三个端口在宿主机上不一定是空闲的——nginx、其他 Web 服务、甚至 Docker 自带的某些服务都可能把它们占掉。我的习惯是在正式启动之前先检查一下宿主机的端口占用情况。ss -tlnp | grep -E :(80|443|22)\b如果被占用可以在docker run或 compose 配置里做端口映射把容器内的 80 映射到宿主机的 8080443 映射到 844322 映射到 2222。这么做不会影响 GitLab 内部的服务逻辑只是让你从外部访问时换一个入口端口。但要注意改了 SSH 端口之后GitLab 生成的克隆地址默认还是 22 端口这就需要额外修改配置了。这个细节我后面专门讲因为十个新手有九个在这里栽跟头。3. 两种落地方式命令行快速验证和编排文件上生产3.1 快速验证一条 docker run 的背后如果你只是想在一台机器上先跑起来看看用docker run是最直接的。下面是我常用的命令docker run -d \ --name gitlab \ --restart always \ -p 8080:80 \ -p 8443:443 \ -p 2222:22 \ -v /srv/gitlab/config:/etc/gitlab \ -v /srv/gitlab/logs:/var/log/gitlab \ -v /srv/gitlab/data:/var/opt/gitlab \ gitlab/gitlab-ce:latest逐行拆开看-d后台运行。--name gitlab给容器起名字方便后续docker exec、docker logs。--restart always服务器重启之后容器自动拉起。这是一个容易被忽略但非常重要的参数不加的话机器一重启 GitLab 就是停止状态。-p 8080:80、-p 8443:443、-p 2222:22宿主机端口和容器端口的映射。-v挂载的三个命令对应我前面说的配置、日志、数据目录。gitlab/gitlab-ce:latestCommunity Edition 社区版镜像。企业版则是gitlab/gitlab-ee。镜像拉取完成后容器并不会立刻可用。GitLab 首次启动要做初始化包括生成密钥、初始化数据库、编译资源这个过程视机器性能而定一般 3 到 10 分钟不等。期间访问页面会显示 502 或者 504这是正常的不要慌。可以用下面的命令看日志确认进度。docker logs -f gitlab看到日志中出现gitlab Reconfigured!或者类似提示时再打开浏览器访问。第一次打开会要求设置 root 密码如果没看到这个页面往下看初始化配置部分。3.2 生产推荐docker-compose 让配置可版本化docker run适合验证但不利于长期运维管理因为所有参数都散在命令行里时间一长就忘了当初是怎么起的。上了生产环境我强烈建议用docker-compose.yml把编排配置固化成文件这样既能版本管理又能一键重建容器。下面是一份我优化过多次的 compose 配置可以直接抄version: 3.8 services: gitlab: image: gitlab/gitlab-ce:latest container_name: gitlab restart: always hostname: gitlab.example.com environment: GITLAB_OMNIBUS_CONFIG: | external_url http://gitlab.example.com:8080 gitlab_rails[gitlab_shell_ssh_port] 2222 gitlab_rails[smtp_enable] true gitlab_rails[smtp_address] smtp.example.com gitlab_rails[smtp_port] 465 gitlab_rails[smtp_user_name] gitlabexample.com gitlab_rails[smtp_password] your-smtp-auth-code gitlab_rails[smtp_domain] example.com gitlab_rails[smtp_authentication] login gitlab_rails[smtp_tls] true ports: - 8080:80 - 8443:443 - 2222:22 volumes: - /srv/gitlab/config:/etc/gitlab - /srv/gitlab/logs:/var/log/gitlab - /srv/gitlab/data:/var/opt/gitlab shm_size: 2g这里重点说一下GITLAB_OMNIBUS_CONFIG环境变量。它相当于把原来写在/etc/gitlab/gitlab.rb里的配置通过环境变量直接注入到容器内的配置文件中。容器第一次启动时omnibus 会把这段配置合并进 gitlab.rb然后执行 reconfigure。这个方式的好处是你的配置跟镜像一起管理重建容器后配置依然在。hostname字段也很关键最好和你规划的访问域名保持一致否则 GitLab 生成的仓库克隆地址会变成一串容器 ID非常难看。如果暂时没有域名可以先写成服务器的 IP 地址。3.3 首次启动后的初始化root 密码在哪里GitLab 新版默认会在初始化时生成一个随机 root 密码放在容器内的/etc/gitlab/initial_root_password文件里。容器启动完成、页面能打开之后执行docker exec -it gitlab cat /etc/gitlab/initial_root_password拿到密码就能用 root 登录。注意这个文件在首次重配置后会被 GitLab 自动删除所以如果你错过了也别慌可以直接重置密码。重置 root 密码的方式是进入容器用 Rails console 修改docker exec -it gitlab gitlab-rails console在 console 里执行user User.find_by(username: root) user.password 新密码 user.password_confirmation 新密码 user.save!退出后重新登录就能用新密码了。这条路在忘了密码、或者 initial_root_password 文件已经被删除时非常有用。还有一点GitLab 初始化时会检查external_url。如果你没有通过 compose 里的环境变量设置它默认用http://gitlab.example.com这种从 hostname 推导出来的地址。如果你只打算用 IP 访问记得把 external_url 改成 IP 加端口的形式否则地址栏会出现奇怪的主机名。4. 部署完不能忽略的三件小事克隆地址、邮件和 HTTPS4.1 让克隆地址变成你希望的样子GitLab 页面里的仓库地址是基于external_url和gitlab_shell_ssh_port这两个配置生成的。很多人在浏览器里打开了页面也创建了仓库但一点“克隆”按钮发现 SSH 地址还是gitgitlab.example.com:2222/...甚至git容器ID:...一脸懵。原因很简单SSH 端口被映射到了宿主机的非标准端口但 GitLab 并不知道这个映射它对外展示的 SSH 端口还是默认的 22。解决办法是在 omnibus 配置里显式告诉它external_url http://gitlab.example.com:8080 gitlab_rails[gitlab_shell_ssh_port] 2222然后执行重配置让配置生效docker exec -it gitlab gitlab-ctl reconfigure重配置之后GitLab 生成的 SSH 克隆地址就会带上:2222用户 clone 时也不再需要用-p 2222的参数去手动指定端口了。这一步虽然简单但能省掉团队成员非常多的困惑。4.2 邮件通知SMTP 配置里的授权码陷阱GitLab 默认是不发邮件的如果不配置 SMTP密码找回、邮件通知、流水线提醒等功能全部形同虚设。配置 SMTP 时最容易踩的坑是“密码”到底填什么。现在主流邮箱服务商基本上都不允许用明文邮箱密码来登录 SMTP而是需要单独开启 SMTP 服务并生成授权码。很多人在配置里填了自己的邮箱登录密码结果 GitLab 日志里报authentication failed折腾半天还以为是端口或者防火墙问题。正确的配置路径是先去邮箱设置里开启 SMTP 服务拿到授权码然后把授权码填到gitlab_rails[smtp_password]里。我习惯把 SMTP 配置写在GITLAB_OMNIBUS_CONFIG环境变量里这样 compose 文件就是唯一配置源不用单独进容器改文件。配置完成后在容器里运行docker exec -it gitlab gitlab-rails console在 console 里发送一封测试邮件Notify.test_email(收件邮箱, 测试标题, 测试正文).deliver_now如果收到邮件说明 SMTP 配置成功。如果报错优先去看docker logs gitlab里的相关日志一般都会有明确的认证失败或者连接超时提示。4.3 HTTPS有域名和没域名是两种玩法如果你有自己的域名最省事的做法是启用 GitLab 内置的 Lets Encrypt 自动续期。只需要在 omnibus 配置里把external_url改成 https并设置letsencrypt[enable] trueGitLab 会在首次启动时自动申请证书并配置 Nginx。注意前提是域名 A 记录已经指向这台服务器且 80 和 443 端口从外部能访问到因为证书签发的验证过程会临时占用 80 端口做验证。如果没有域名只能用 IP 加端口访问我建议在最外层加一层反向代理来处理 HTTPS而不是直接在 GitLab 容器上折腾自签名证书。因为自签名证书在浏览器里会被拦团队成员每次访问都要加信任体验很差而且一旦解析器升级又容易出各种奇怪问题。反向代理只负责把 443 端口的请求转发到 GitLab 容器的 HTTP 端口证书集中管理维护上轻松很多。无论用哪种方式都尽量不要让用户裸用 HTTP 访问尤其是仓库涉及账号密码和敏感代码。国内网络环境下可能觉得多一层代理麻烦但安全性上是值得的。5. 升级与迁移最怕的是在风平浪静时大意5.1 升级的铁律先备份再看版本跨度GitLab 升级出问题的概率不高但一出就是大问题。比如数据库迁移卡住、后台 worker 版本不兼容、或者大版本升级后配置格式失效。我给自己定的规矩是升级之前必做备份且备份文件要落到宿主机上不能只在容器里。GitLab 自带的备份命令是docker exec -it gitlab gitlab-backup create备份文件默认生成在/var/opt/gitlab/backups目录下也就是挂载目录/srv/gitlab/data/backups中。备份文件名形如1803456789_2025_01_10_16.9.1_gitlab_backup.tar。然后还要单独备份配置文件/etc/gitlab/gitlab.rb和/etc/gitlab/gitlab-secrets.json。前者是配置后者是加密密钥文件。很多人只备份了数据忘了 secrets 文件结果恢复之后数据能读出来但对外的 Webhook、Runner 注册令牌等全部失效。备份命令执行结束后我会检查 backups 目录里有没有生成新的 tar 包、文件大小是否合理。备份 30G 的仓库如果只出来几 MB 的文件那八成是备份过程有问题要仔细看日志。升级操作也不复杂只需要修改镜像 tag。小版本升级可以直接一两个版本往上跳但大版本升级必须按官方支持的升级路径来不能跨越太多版本否则数据库结构迁移不兼容。以 16.x 升 17.x 为例我一般会先升到 16 的最新补丁版等稳定了再升 17。如果版本跨度太大中间还要重新起一个中间版本的容器跑一次配置和数据库迁移再继续升。5.2 整体迁移用“搬目录”代替备份恢复GitLab 官方推荐的迁移方式是备份 恢复但我的实践是如果是整个服务器搬迁直接连目录搬家比备份恢复更省事也更稳妥。因为备份恢复受备份完整性影响而且恢复过程要重新执行 reconfigure、重新初始化数据库缓存耗时长。我的迁移路子大致是这样停止旧服务器上的 GitLab 容器确保数据文件不再写入docker stop gitlab打包三个挂载目录tar -czf gitlab-full.tar.gz /srv/gitlab/config /srv/gitlab/logs /srv/gitlab/data把压缩包传到新服务器。在新服务器上解压回同样路径保持目录结构一致。在新服务器上用相同版本的镜像和完全相同的 compose 文件启动容器docker compose up -d启动后观察日志如果旧版本和新版本一致基本几分钟内就能访问。如果不一致就需要把版本拉齐或者走官方升级/降级路径。这套做法的核心前提是新旧服务器的挂载路径要一致compose 文件里设置的端口和环境变量尽量保持一致不要图省事改端口。提示跨平台迁移时如果旧服务器上 GitLab 内部生成的文件权限是通过 UID 存储的新服务器上 Docker 的权限映射通常不会变因为容器内 UID 是固定的。真正可能出问题的是你在宿主机层面 chown 过某些目录迁移之后要重新确认一遍所属关系。6. 上线后的日常运维日志、权限和资源是一场持久战6.1 启动慢、504、容器重启多半是资源问题GitLab 启动完成后页面偶尔会出现 504 或者很长的加载过程。如果容器没有重启那大概率是内存不足导致后端进程在频繁 GC 或者被 OOM。排查第一步是看容器状态docker stats gitlab docker logs gitlab --tail 100如果日志里出现Killed或者OOMKilled说明内存确实不够了。优先给服务器加内存或加 swap。shm_size配置到 2G 以上也是 GitLab 官方推荐的因为 Git 操作涉及共享内存太小会导致诸如502、上传失败等问题。6.2 文件权限问题不是玄学是 UID 映射GitLab 在容器内会创建git用户而挂载到宿主机的数据目录在宿主机上看到的 owner 可能是 998 这个 UID。如果运维人员手动去改了这些目录的属主比如 chown 成宿主机用户反而会让容器内进程失去访问权限。我自己就被这种自作聪明坑过。当时嫌挂载目录的属主看着“奇怪”把整个 data 目录 chown 到了 root然后 GitLab 就报仓库目录不可写。排查到最后才发现问题出在这里把属主改回去就恢复了。遇到权限相关故障先不要乱改权限。到容器里看 GitLab 认为目录属主应该是什么docker exec -it gitlab ls -ln /var/opt/gitlab/git-data然后在宿主机上比对 UID再决定要不要 chown 或者 chmod。记住容器的视角才是权威宿主机上的显示只是数字。6.3 磁盘爆满日志和备份都在偷偷吃空间GitLab 的日志和其他服务一样会持续增长如果不做轮转或者清理一两个月就能吃掉几十 G。虽然我们挂载了/var/log/gitlab到宿主机但很多人从来没有看过这个目录的体积。我一般用下面的命令快速排查du -sh /srv/gitlab/logs/* du -sh /srv/gitlab/data/backups/*日志可以靠 GitLab 自带的 logrotate 自动轮转配置在 gitlab.rb 里有相关参数也可以直接减小logrotate_frequency的周期。备份则是定期删除过期备份比如保留最近 7 天。7. 我的固定部署流程和一些不成文的经验最后分享一下我这几次成功部署后固定下来的操作序列。如果你嫌前面的分析麻烦直接照这个流程走能避开大部分常见的坑先在宿主机上建好三个目录/srv/gitlab/{config,logs,data}并检查 80、443、22 端口占用情况。写一份 compose 文件固定 external_url、SSH 端口、SMTP 配置。用 compose 启动容器等待初始化完成查看日志直到确认 GitLab 可用。登录页面设置 root 密码或从 initial_root_password 读取。验证 SSH 克隆地址是否如预期验证 SMTP 测试邮件能收到。进管理后台关闭不需要的注册开启、设置项目可见性对外开放策略按团队需要收紧。配置好监控磁盘和内存的告警脚本把备份加入 crontab 定期执行。我在实际使用中最大的体会是GitLab 本身已经足够成熟出问题很少是因为功能缺陷大部分都是运维层面的选择错误比如端口没规划、权限乱动、升级不备份。因此比 “安装” 更重要的是“持续运维”。最后再分享一个小技巧每次升级或者重装之前我都习惯把当时的 compose 文件、GitLab 版本、上个版本的配置改动记一条简单笔记。这个习惯救过我很多次在遇到诡异问题需要回退时你才会发现版本和配置的可溯源性比想象中更重要。