GitLab 私有化部署实战 ------ 从 Omnibus 到 Docker 的踩坑之旅
时间 :2026-08-31 至 2026-09-01
服务器 :Ubuntu 22.04.5 LTS(Jammy) / Intel Xeon E5-2620 v3 × 2 / 16 GB /
/data800 GB目标 :5 人内部研发团队的代码托管平台
最终方案 :
gitlab/gitlab-ce:19.3.1-ce.0官方 Docker 单容器镜像 + Bind Mount 到/data
0. 写在前面
这次部署看似简单("装个 GitLab 而已"),但实际踩到了几个深坑:
- GLIBC 跨版本兼容性 ------ 让 19.3.1 和 17.11.7 两个版本都装不了
- 国内网络限速 ------ 1.4 GB 的 deb 从 packages.gitlab.com 死活下不动
- 概念上的自我绊倒 ------ 我 + Claude 起初误判 Docker 路线违规,最终发现它反而最干净
本博客完整记录这三个坑,最终如何走到"一条命令启容器"的稳定状态。
1. 需求与硬约束
1.1 业务侧
- 团队规模:5 名开发人员
- 用途:内部代码托管、Merge Request、Issue、CI/CD
- 后续计划:接入 DeepSeek API 做 AI Code Review(不在本次部署范围)
1.2 部署侧
| 项 | 值 |
|---|---|
| 内核 OS | Ubuntu 22.04 LTS(无法升级到 24.04) |
| 服务器等级 | Intel Xeon × 2 / 12C24T / 16 GB RAM / 4 GB Swap |
根 / 容量 |
295 GB(LVM,11 GB 已用) |
数据 /data |
801 GB(关键:业务数据要求落这里) |
| 网络 | 192.168.1.100/24(RFC1918 内网)/ 无公网域名 / 无 SSL 证书 |
| 已装服务 | Docker 29.7.2(data-root: /data/docker) |
| GitLab 用途 | CE 版 |
| 版本 | 要求 19.3 分支的最新 patch(当时为 19.3.1) |
1.3 我自己设的硬规则
部署前我对自己提了若干约束(事后看其中有些太僵):
- ✅ 用 Omnibus,不源码编译
- ❌ 不许"为了方便把 PG/Redis 拆成一堆 Docker 容器"
- ❌ 不许用第三方镜像
- ❌ 不擅自装 Kubernetes / Runner / AI Gateway
- ⚠️ 所有状态数据落
/data,根目录只放只读的 Omnibus 安装包
事后证明第 2 条太严 ------ "不拆服务"≠"不用 Docker",用 GitLab 官方 Omnibus 单容器镜像并不违反这条约束。详见第 6 节。
2. 第一版方案:Omnibus Linux Package
我用 Claude 规划了一个 8 步脚本 /tmp/gitlab-setup.sh,核心设计:
bash
external_url 'http://192.168.1.100'
data_dir '/data/gitlab' # 把所有增长数据迁过去
gitlab_rails['gitlab_shell_ssh_port'] = 22 # 复用主机 sshd
gitlab_rails['signup_enabled'] = false # 关闭公开注册
gitlab_rails['project_default_visibility_level'] = 0 # 项目默认私有
registry['enable'] = false # 不要 Container Registry
/tmp/gitlab-setup.sh 大致逻辑:
apt updateapt install openssh-server curl ca-certificates tzdata perlEXTERNAL_URL=http://192.168.1.100 apt-get install -y /tmp/gitlab-ce_19.3.1-ce.0_amd64.debgitlab-ctl stop- 备份默认
gitlab.rb→ 替换成我们的版本 mv /var/opt/gitlab /data/gitlab(迁数据)gitlab-ctl reconfigure
这是教科书式的 GitLab 升级到自定义数据目录的标准做法。
3. 第一道坎:GLIBC_2.38
执行到第 3 步时 dpkg 抛错:
Setting up gitlab-ce (19.3.1-ce.0) ...
/opt/gitlab/embedded/bin/ruby:
/lib/x86_64-linux-gnu/libm.so.6:
version `GLIBC_2.38' not found
(required by /opt/gitlab/embedded/lib/libruby.so.3.3)
/opt/gitlab/embedded/bin/ruby:
/lib/x86_64-linux-gnu/libc.so.6:
version `GLIBC_2.38' not found
(required by /opt/gitlab/embedded/lib/libruby.so.3.3)
dpkg: error processing package gitlab-ce (--configure):
installed gitlab-ce package post-installation script subprocess returned error exit status 1
立刻验证:
bash
$ ldd --version
ldd (Ubuntu GLIBC 2.35-0ubuntu3.14) 2.35
$ objdump -T /opt/gitlab/embedded/lib/libruby.so.3.3 | grep GLIBC_2.38 | head
GLIBC_2.38 __isoc23_strtoll
GLIBC_2.38 __isoc23_strtol
GLIBC_2.38 fmod
GLIBC_2.38 strlcat
GLIBC_2.38 strlcpy
Ubuntu 22.04(Jammy)的系统 glibc 是 2.35 ,而 GitLab 19.3.1 里嵌入式 Ruby 3.3 的二进制链接的是 GLIBC_2.38。
3.1 为什么不能绕过
GLIBC 符号是运行时解析 ,不能用 LD_LIBRARY_PATH 替;不能用静态链接绕;也不能用容器里的 chroot 替。只有两个出路:
- 升级操作系统到 Ubuntu 24.04(提供 glibc 2.39)------ 跨大版本升级有风险,要重启
- 用一个内置 glibc 已经够老的 GitLab 版本 ------ 我先试这条路
3.2 半装状态的处理
gitlab-ce 包处于 install ok half-configured,需要:
bash
sudo dpkg --remove --force-remove-reinstreq gitlab-ce
sudo apt-get purge -y gitlab-ce
sudo rm -rf /etc/gitlab /var/opt/gitlab /var/log/gitlab /opt/gitlab
sudo apt-get autoremove -y
4. 第二道坎:降级 GitLab 也装不上
我让 Claude 帮我列出 jammy 仓库所有 gitlab-ce 版本:
bash
curl -s "https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu/dists/jammy/main/binary-amd64/Packages.gz" \
| gunzip | awk -v RS= '/^Package: gitlab-ce/' | grep '^Version:' \
| awk '{print $2}' | sort -V | uniq | wc -l
398
398 个版本,从 15.5.0 一直到 19.3.1。我从高到低选了 17.11.7(最后一个 17.x LTS patch),本机下载验证 GLIBC:
bash
$ dpkg-deb -x gitlab-ce_17.11.7-ce.0_amd64.deb /tmp/g17x/extracted
$ for so in /tmp/g17x/extracted/opt/gitlab/embedded/lib/libruby.so.3.2.5 \
/tmp/g17x/extracted/opt/gitlab/embedded/bin/redis-server \
/tmp/g17x/extracted/opt/gitlab/embedded/bin/postgres; do
refs=$(objdump -T "$so" 2>/dev/null \
| grep -oE 'GLIBC_[0-9]+\.[0-9]+' | sort -V | uniq -c | tr '\n' ' ')
printf " %-30s %s\n" "$(basename $so)" "$refs"
done
libruby.so.3.2.5 1 GLIBC_2.35 5 GLIBC_2.38
redis-server 18 GLIBC_2.34 6 GLIBC_2.38
postgres 12 GLIBC_2.34 8 GLIBC_2.38
17.11.7 也需要 GLIBC_2.38 。GitLab 在 17.x 中后期切到了 Ruby 3.2 + 新 glibc,远比 19.x 切得更早。换言之,17.11.x 以下的某个版本才是 Ubuntu 22.04 能装的最后窗口,但精确版本号只能继续反复下载 deb 试探。
4.1 网络瓶颈
更糟的是,中国大陆出口到 storage.googleapis.com(packages.gitlab.com 的实际存储后端)非常不稳定:
bash
$ curl -L -o /dev/null --max-time 60 ...gitlab-ce_17.11.7-ce.0_amd64.deb
... 600+ MB downloaded, then EOF or indefinite stall
阿里云 / Tuna / USTC 等国内镜像没有同步 packages.gitlab.com 的 Debian pool。每次下载都重 1+ GB,反复试探候选版本非常不现实。
5. 决策点:升级系统 vs 退版本 vs Docker
我有三个选项:
| 方案 | 优势 | 劣势 |
|---|---|---|
| A. 系统升级到 Ubuntu 24.04 | 装回 19.3.1 | 需 do-release-upgrade,要重启,2 GB /boot 紧贴边需要 autopurge |
| B. 继续退版本 17.5.x / 17.3.x | 不动 OS | 反复下载 1+ GB 防 GLIBC,国内网络反复超时 |
| C. 转 Docker Omnibus 单容器镜像 | 镜像内自带 Ubuntu 24.04 userspace,glibc 2.39;一次性解决 GLIBC;保留 Omnibus 全部能力 | 与最初的"Linux Package"计划偏离 |
最后我选了 C。回头看这是唯一干净的实现:
- 镜像是 GitLab Inc 自家 publish,不是"第三方镜像"
- 单镜像单容器运行完整 Omnibus:PG / Redis / Puma / Sidekiq / Gitaly / Nginx / Prometheus 全在容器内,零拆分
- 容器内 glibc 是它自己的(2.39),与宿主机 OS 完全无关
- 我的两个核心约束(全在 /data、保持 Omnibus)都满足 ------ 用 bind mount 把容器内
/var/opt/gitlab挂到宿主机/data/gitlab/data
6. Docker 部署实战
6.1 事前准备
主机上预创建卷:
bash
sudo mkdir -p /data/gitlab/{config,data,logs}
sudo chmod 755 /data/gitlab/{config,data,logs}
6.2 拉镜像(中途翻车了 1 次)
bash
sudo docker pull gitlab/gitlab-ce:19.3.1-ce.0
中途遇到 docker: short read: expected 161 bytes but got 0: unexpected EOF ------ GFW 截断,重试一次就成功了:
19.3.1-ce.0: Pulling from gitlab/gitlab-ce
3dfcc7a2be79: Pull complete # 之前那次下载的层
...
Digest: sha256:f63df4c43029fe91db370609c0b40a1e3585cebd06e3e9637d93a9a3030eb86e
Status: Downloaded newer image for gitlab/gitlab-ce:19.3.1-ce.0
6.3 启动容器
bash
sudo docker run -d \
--name gitlab \
--hostname gitlab-server \
--restart always \
-p 80:80 -p 443:443 -p 2222:22 \
-v /data/gitlab/config:/etc/gitlab \
-v /data/gitlab/data:/var/opt/gitlab \
-v /data/gitlab/logs:/var/log/gitlab \
gitlab/gitlab-ce:19.3.1-ce.0
关键设计点:
2222:22:容器内 sshd 自管 SSH-git,宿主 SSH 仍占 22。运维登录走 22,git clone走 2222。- 三个
-v分别挂配置、数据、日志,全部落/data。 - 容器 entrypoint 第一次启动会自动跑
gitlab-ctl reconfigure,约 3--5 分钟。
6.4 跟进状态
bash
sudo docker ps --filter name=gitlab
# NAMES STATUS PORTS
# gitlab Up 4 minutes (healthy) 0.0.0.0:80->80, ... 0.0.0.0:2222->22/tcp
sudo docker exec gitlab gitlab-ctl status
# run: alertmanager: (pid 2694) ...
# run: gitaly: ...
# run: postgresql: ...
# run: puma: ...
# run: sshd: ...
# ... 14 个组件全 run
6.5 gitlab.rb 改造
原来 Linux Package 版的 gitlab.rb.new 不能 直接用:里面有一行 data_dir '/data/gitlab',在容器视角会把数据写到容器内存层(不映射回主机),重启即丢。SSH 端口也要改成 2222。
最终版(/data/gitlab/config/gitlab.rb):
ruby
# 不再设 data_dir:让容器默认的 /var/opt/gitlab 走 bind mount
external_url 'http://192.168.1.100'
gitlab_rails['gitlab_shell_ssh_port'] = 2222
gitlab_rails['signup_enabled'] = false
gitlab_rails['project_default_visibility_level'] = 0
gitlab_rails['group_default_visibility_level'] = 0
gitlab_rails['backup_keep_time'] = 604800 # 7 天
registry['enable'] = false
registry_external_url nil
bash
sudo cp /tmp/gitlab.rb.new /data/gitlab/config/gitlab.rb
sudo chmod 600 /data/gitlab/config/gitlab.rb
sudo docker exec -it gitlab gitlab-ctl reconfigure
7. 首次登录 + 邮箱修复
7.1 root 初始密码
容器已把密码写到 bind mount 那个文件:
bash
sudo cat /data/gitlab/config/initial_root_password
这一份 24 小时后会自删。建议改 root 密码后再也不依赖这个文件。
7.2 root 邮箱替换(绕过旧邮箱验证)
默认 root 邮箱是 gitlab_admin_db360f@example.com,改邮件时 web UI 要给旧 邮箱发验证码,但 example.com 是占位域。所以用 Rails 后台直接改:
bash
sudo docker exec -it gitlab gitlab-rails runner '
u = User.find_by(username: "root")
u.email = "<您的实际邮箱>"
u.skip_reconfirmation!
u.save!
puts "OK email=#{u.email}"
'
skip_reconfirmation! 跳过整个邮件验证流程。验证:
bash
sudo docker exec -it gitlab gitlab-rails runner \
'puts "root email = #{User.find_by(username: \"root\").email}"'
# root email = <您的实际邮箱>
刷一下 web UI,root 用户的 Edit profile 里 Email 应已显示新邮箱。
8. 踩坑清单与经验
8.1 GLIBC vs Debian 包的常见错觉
packages.gitlab.com/gitlab/gitlab-ce 仍然给 jammy 发 deb,看起来支持 Ubuntu 22.04。但 deb 的元数据层面 只声明 Depends: openssh-server, perl ------不检查内嵌二进制的 glibc 版本。看起来"装得上"实际上 install hook 一行 glibc 检查就崩。
教训:
- 装之前先
dpkg-deb -x抽出内嵌二进制,验objdump -T | grep GLIBC_2.xx。别相信 deb 元数据。 - 跨大版本 GitLab 时,先确认系统 glibc:
bash
ldd --version | head -1
| 系统 | glibc |
|---|---|
| Ubuntu 20.04 focal | 2.31 |
| Ubuntu 22.04 jammy | 2.35 ← 我们这台 |
| Ubuntu 24.04 noble | 2.39 |
| GitLab | 所需最低 glibc |
|---|---|
| 17.5 及以下 | 2.31+ |
| 17.6 ~ 17.11 | 2.38+ ← 切点在这区间内 |
| 18.x / 19.x | 2.38+ |
如果 GitLab 官方将来给出精确表格会更稳。
8.2 "Docker = 拆服务"的误判
我当时把这条规则过度使用了。正确的边界是:
只允许 :
gitlab/gitlab-ce单容器镜像(全 Omnibus)。不允许 :
postgresql + redis + nginx + gitlab-rails拆成多个容器。
这两条在 GitLab 官方全是 supported deployment,但前者合规、后者违反我们的"集中管理"约束。
容器化后反而没有违反"Omnibus 集中管理"这条核心目标,因为它内部仍然是 Omnibus。
8.3 国内网络下的 deb 下载技巧
packages.gitlab.com的实际文件在storage.googleapis.com/packages-ops/,GFW 经常截大文件- 国内镜像(阿里 / Tuna / USTC)没有镜像这个仓库
- apt 自带 HTTP 重试 + 长超时 比
curl --max-time N友好;实在不行就dpkg -i ./x.deb用本地文件
8.4 容器里的 gitlab.rb 与 host 的关系
容器版 gitlab.rb 与 Linux Package 版唯一差异:
| 维度 | Linux Package | Docker 容器 |
|---|---|---|
data_dir |
必须配置到 /data/gitlab |
不要设 (让容器默认 /var/opt/gitlab 走 bind mount) |
gitlab_shell_ssh_port |
通常是 22(与 sshd 共享) | 设成宿主的 -p XXXX:22 中的 XXXX |
其他完全一致。
8.5 Bind Mount 模式选择
我选了 bind mount (-v /data/gitlab/...:/var/opt/gitlab),不用 Docker volume。理由:
- bind mount 的目录在 host 上直接可见 ,运维(
ls、du、cp、backup)方便 - Docker volume 需要
docker volume inspect才看得到,且跟随容器生命周期
bind mount 的代价是首次启动前要 mkdir,否则 Docker 会以 root 创建。
8.6 不在这次部署范围里的事
| 项 | 决策 |
|---|---|
| AI Code Review(DeepSeek API) | 延后,等基础平台稳定 |
| GitLab Runner | 延后,避免与 GitLab 主进程抢 16 GB 内存 |
| Container Registry | 关闭,需要时再启用 |
| Kubernetes / KAS 多集群 | 不接,单实例足够 |
| 监控 / Prometheus | 保留 Omnibus 默认(容器自动带了) |
9. 最终架构
┌─────────────────────────────────────────────────────────────┐
│ Host: gitlab-server (Ubuntu 22.04 / 16 GB / LVM) │
│ │
│ ┌── sshd :22 (host 原生, 运维登录) │
│ ├── nginx host firewall: iptables empty, no ufw │
│ └── Docker daemon │
│ │ │
│ └── container "gitlab" (gitlab/gitlab-ce:19.3.1) │
│ ├── sshd :22 (容器内 GitLab SSH) │
│ ├── nginx :80 (→ http://192.168.1.100) │
│ ├── puma / sidekiq / gitaly / postgres / redis │
│ └── /etc/gitlab ← bind → /data/gitlab/config │
│ /var/opt/gitlab ← bind → /data/gitlab/data │
│ /var/log/gitlab ← bind → /data/gitlab/logs │
└─────────────────────────────────────────────────────────────┘
9.1 数据布局(host 视角)
/data/
├── docker/ # 宿主机已有 Docker 数据
└── gitlab/ # GitLab 卷
├── config/ # gitlab.rb, secrets, initial_root_password
├── data/ # PG, Redis, repositories, lfs, uploads, backups
└── logs/ # 全组件日志
9.2 备份策略(下一步要做的)
- 容器内
gitlab-rake gitlab:backup:create→/data/gitlab/data/backups/ - cron 每日 02:00 跑
gitlab_rails['backup_keep_time'] = 604800(保留 7 天)- 额外每周打包:
/etc/gitlab/gitlab.rb/etc/gitlab/gitlab-secrets.json/etc/gitlab/gitlab-registry-*-key.json(如果启用 registry)/etc/gitlab/ssl/*(如果启用 TLS)
9.3 安全基线(已经做 + 接下来要补)
| 项 | 状态 |
|---|---|
signup_enabled = false |
✅ 已配 |
| 项目 / Group 默认 visibility = private | ✅ 已配 |
| Container Registry 关闭 | ✅ 已配 |
| SSH 端口定制(2222) | ✅ 已配 |
| root 邮箱绑定到真实邮箱 | ✅ 已改 |
| 用户 2FA 强制 | ⏳ 进入 Admin Area 改设置 |
| 公开注册页面已禁 | ⏳ 通过 UI 二次确认 |
| 密码策略(长度/复杂度) | ⏳ 进入 Settings → General 加 |
10. 后续行动清单
- 为 5 名成员创建用户(root → Admin Area → Users)
- 强制全员 2FA
- 写每日 02:00 backup cron
- 在 web UI 创建第一个 Group + Project 跑通 clone / push / MR
- 验证 SSH git 通道:
ssh -T -p 2222 git@192.168.1.100 - 评估是否需要给外部访问开个 Nginx 反向代理 + 真实域名
11. 教训汇总
- deb 元数据不验证 glibc 版本 ------ 装之前必须
objdump -T extracted/opt/gitlab/embedded/lib/libruby.so.3.x看一眼 - 国内出口到 GCS 长期糟糕 ------ 大 deb 反复下载是实际痛点,Docker 镜像也一样要重试
- "用 Docker 拆服务"的边界要清楚 ------ 单容器 Omnibus 镜像不是拆分
initial_root_password是真·临时文件 ------ 24h 后自删,改造密码的功夫不能省- 邮件验证的 bypass 路径要熟 ------
User.find_by(...).update(skip_reconfirmation!)是 GitLab 运维基本功 data_dir在容器里是陷阱 ------ 不要设
至此,GitLab 19.3.1 在 Docker 上稳定运行,所有 14 个组件健康,HTTP 在
http://192.168.1.100可访问,SSH Git 通过 2222 端口可达。Phase 5--8(数据目录验证、防火墙、备份 cron、终态报告)继续推进中。