GitLab 私有化部署实战 —— 从 Omnibus 到 Docker 的踩坑之旅

GitLab 私有化部署实战 ------ 从 Omnibus 到 Docker 的踩坑之旅

时间 :2026-08-31 至 2026-09-01

服务器 :Ubuntu 22.04.5 LTS(Jammy) / Intel Xeon E5-2620 v3 × 2 / 16 GB / /data 800 GB

目标 :5 人内部研发团队的代码托管平台

最终方案 :gitlab/gitlab-ce:19.3.1-ce.0 官方 Docker 单容器镜像 + Bind Mount 到 /data


0. 写在前面

这次部署看似简单("装个 GitLab 而已"),但实际踩到了几个深坑:

  1. GLIBC 跨版本兼容性 ------ 让 19.3.1 和 17.11.7 两个版本都装不了
  2. 国内网络限速 ------ 1.4 GB 的 deb 从 packages.gitlab.com 死活下不动
  3. 概念上的自我绊倒 ------ 我 + 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 大致逻辑:

  1. apt update
  2. apt install openssh-server curl ca-certificates tzdata perl
  3. EXTERNAL_URL=http://192.168.1.100 apt-get install -y /tmp/gitlab-ce_19.3.1-ce.0_amd64.deb
  4. gitlab-ctl stop
  5. 备份默认 gitlab.rb → 替换成我们的版本
  6. mv /var/opt/gitlab /data/gitlab(迁数据)
  7. 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 替。只有两个出路:

  1. 升级操作系统到 Ubuntu 24.04(提供 glibc 2.39)------ 跨大版本升级有风险,要重启
  2. 用一个内置 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. 教训汇总

  1. deb 元数据不验证 glibc 版本 ------ 装之前必须 objdump -T extracted/opt/gitlab/embedded/lib/libruby.so.3.x 看一眼
  2. 国内出口到 GCS 长期糟糕 ------ 大 deb 反复下载是实际痛点,Docker 镜像也一样要重试
  3. "用 Docker 拆服务"的边界要清楚 ------ 单容器 Omnibus 镜像不是拆分
  4. initial_root_password 是真·临时文件 ------ 24h 后自删,改造密码的功夫不能省
  5. 邮件验证的 bypass 路径要熟 ------ User.find_by(...).update(skip_reconfirmation!) 是 GitLab 运维基本功
  6. data_dir 在容器里是陷阱 ------ 不要设

至此,GitLab 19.3.1 在 Docker 上稳定运行,所有 14 个组件健康,HTTP 在 http://192.168.1.100 可访问,SSH Git 通过 2222 端口可达。Phase 5--8(数据目录验证、防火墙、备份 cron、终态报告)继续推进中。

相关推荐
VIP_CQCRE13 小时前
给团队搭一个 AI 工作台:Open WebUI 接入 Ace Data Cloud 实战
docker·ai·rag·open webui·ace data cloud
程序员老赵16 小时前
Docker 部署 Portainer CE:轻松搭建容器管理平台
docker·容器·开源
TTATTC16 小时前
Gmail、Drive、日历共用一套命令:gws 如何在运行时把 Discovery 文档编译成 CLI
docker·开源
大侠归来17 小时前
Docker 部署 Dolibarr:从零开始的完整指南
运维·docker·容器
七月吃橘子17 小时前
WSL2+Docker+Ollama,本地部署大模型实践(Qwen1.8b)
运维·docker·容器
Nontee18 小时前
K3s vs K8s:差的不是功能,是交付方式
云原生·容器·kubernetes
_可乐无糖18 小时前
GitLab CI 脚本之 CI_COMMIT_AUTHOR
ci/cd·gitlab
做运维的阿瑞18 小时前
Docker常见故障全方位排查指南
运维·docker·云原生·容器
木子欢儿19 小时前
Zabbix 7.4 Alpine 版 Docker Compose 部署
android·运维·docker·容器·zabbix