1. 项目概述:为什么选择 Docker 部署 GitLab,而不是直接安装?
GitLab Docker 方式部署——这六个字背后,藏着过去五年里我帮二十多家中小团队落地代码协作平台时踩过最多、也最值得重讲的一条路。不是因为“Docker 很酷”,而是因为真实场景里,你根本没时间跟 Ruby 环境冲突、PostgreSQL 版本锁死、Redis 配置错位、Nginx 反向代理漏配这些细节死磕三天。某高校实验室曾用源码编译部署 GitLab CE,光是解决bundle install卡在charlock_holmes编译失败就耗掉两个工作日;某初创公司运维同事在 CentOS 7 上手动装 GitLab Omnibus,结果因系统自带 OpenSSL 版本太低,导致 CI/CD runner 无法连接 GitLab 实例,排查到凌晨三点才发现是 TLS 握手协议不兼容。
Docker 部署 GitLab 的本质,是把 GitLab 官方验证过的完整运行时环境(含 GitLab Rails 应用、PostgreSQL 13+、Redis 7、NGINX、Logrotate、Sidekiq、Gitaly 等全部组件)打包成一个可复现、可迁移、可快照的镜像单元。它不解决“GitLab 是什么”,但彻底绕开了“为什么我的 GitLab 启动不了”的八成共性问题。你不需要成为 Ruby 工程师,也不必精通 PostgreSQL 调优,只要理解容器生命周期、端口映射逻辑和持久化路径绑定规则,就能在 12 分钟内让一个功能完整的 GitLab 实例跑起来——包括用户注册、仓库创建、CI 流水线触发、合并请求评审,全部可用。
这个方案特别适合三类人:一是刚接手运维工作的应届生,需要快速交付一个稳定可用的内部代码平台;二是技术负责人,要为多个测试环境(dev/staging/prod)提供一致基线;三是 DevOps 工程师,正推进基础设施即代码(IaC),准备把 GitLab 纳入 Terraform 或 Ansible 自动化流水线。它不适合追求极致性能调优的超大规模企业(日活万级+、单仓超 50GB),也不适合必须与现有 LDAP 架构强耦合且无法改造认证链路的遗留系统——但这两类场景,本就不该从 Docker 部署起步。
我实测过 7 种主流部署方式的首次可用时间(从空服务器开始计时):源码编译(142 分钟)、Omnibus 包安装(38 分钟)、Docker 单容器(11 分钟)、Docker Compose 多服务(19 分钟)、Helm Chart on K8s(27 分钟)、云厂商托管 GitLab(开通即用但不可控)、PaaS 平台一键部署(依赖平台稳定性)。Docker 单容器方案在“可控性”和“速度”之间取得了最务实的平衡点——它不黑盒,所有配置明文可见;它不轻量,但足够自包含;它不替代架构设计,却为后续演进留足接口。接下来的内容,就是我把这套方法沉淀为标准化操作手册的过程,每一步都标注了“为什么这么写”“不这么写会怎样”“线上已验证的参数值”。
2. 整体架构设计与核心选型逻辑
2.1 为什么坚持单容器模式,而非 Docker Compose 拆分服务?
很多人看到 GitLab 官方文档里推荐docker-compose.yml多服务编排(postgres + redis + gitlab),第一反应是“更标准、更云原生”。但我在给某跨境电商 SaaS 公司做技术审计时发现:他们用 Compose 部署的 GitLab,在一次 PostgreSQL 主从切换后,GitLab 容器因连接池未及时刷新而持续报PG::ConnectionBad,整个研发团队中断提交 47 分钟。根本原因在于,GitLab CE 官方镜像本身就是一个预集成运行时——它内部已内置 PostgreSQL 13.12、Redis 7.0.15、NGINX 1.22,所有组件版本经过 GitLab QA 团队交叉验证,通信协议、序列化格式、时区设置全部对齐。强行拆开,等于主动引入跨容器网络延迟、DNS 解析抖动、健康检查不同步三大风险点。
单容器模式的核心优势有三点:
第一,启动原子性。docker run命令执行成功,意味着整个 GitLab 栈(含数据库、缓存、Web 服务)已就绪并完成初始化。而 Compose 模式下,gitlab容器可能先于postgres启动,触发 10 次重试连接失败后才降级为只读状态,此时 Web 界面显示“502 Bad Gateway”,新人误以为部署失败。
第二,配置收敛性。所有关键参数(如external_url、gitlab_rails['db_host'])统一通过-e环境变量注入,无需维护gitlab.rb、postgresql.conf、redis.conf三套配置文件。某金融客户曾因gitlab.rb中gitlab_rails['redis_host']写成redis://redis:6379,而docker-compose.yml里 Redis 服务名定义为cache,导致 Sidekiq 无限重连超时。
第三,升级确定性。docker pull gitlab/gitlab-ce:16.11.5-ce.0拉取的是 GitLab 官方签名镜像,包含已验证的全栈补丁。若拆分为独立服务,需分别升级 PostgreSQL 镜像(可能引入不兼容变更)、Redis 镜像(配置项变动)、GitLab 镜像(需手动同步gitlab.rb中的数据库连接参数),升级窗口期拉长 3 倍以上。
当然,单容器不是银弹。当你的团队规模超过 200 人、日均 CI 任务超 5000 次时,建议迁移到 Helm Chart 模式,利用 Kubernetes 的 Horizontal Pod Autoscaler 对 Sidekiq Worker 进行弹性伸缩。但对 90% 的中小团队而言,“一个容器搞定所有”是最小可行、最大确定性的起点。
2.2 镜像版本选择:CE 还是 EE?Tag 用 latest 还是具体 patch 版本?
GitLab 提供两个官方镜像仓库:gitlab/gitlab-ce(Community Edition,免费开源)和gitlab/gitlab-ee(Enterprise Edition,商业授权)。EE 版本额外包含集群高可用、高级安全扫描、合规审计日志等功能,但需购买许可证。我经手的 32 个部署案例中,仅 3 个选择了 EE——全部是受 SOC2 合规要求驱动的金融科技客户。其余 29 个案例,CE 版本完全满足需求:它支持完整的 CI/CD 流水线、容器镜像仓库、代码质量分析(via Code Quality report)、依赖扫描(Dependency Scanning)、漏洞管理(via Security Dashboard),唯一缺失的是 SAST(静态应用安全测试)的商业规则集,但社区版已集成 Bandit、Brakeman、ESLint 等主流开源扫描器。
关于 Tag 选择,绝对禁止使用latest。GitLab 官方明确声明:latest标签不保证向后兼容,可能指向任意主版本(如 16.x 或 17.x),而主版本升级常伴随数据库迁移脚本变更。某教育科技公司曾因docker run -d --name gitlab gitlab/gitlab-ce:latest拉取到 17.0.0 版本,其数据库迁移脚本要求 PostgreSQL 14+,但容器内嵌的 PG 版本为 13.12,导致启动失败且无法回滚。正确做法是锁定patch 版本号,例如16.11.5-ce.0。这个编号遵循语义化版本规则:16是主版本(大功能迭代),11是次版本(特性新增),5是修订版本(Bug 修复),ce.0表示社区版首个构建。GitLab 每周发布 1~2 个 patch 版本,全部经过自动化回归测试,且向下兼容前一个次版本的所有 API 和数据库结构。
如何获取最新稳定 patch 版本?不要依赖第三方博客或论坛。直接访问 https://hub.docker.com/r/gitlab/gitlab-ce/tags ,按Last updated排序,筛选*-ce.0结尾的标签(如16.11.5-ce.0、16.10.7-ce.0)。注意避开rc(Release Candidate)、pre(Preview)等测试标签。我建立了一个简单的 Shell 脚本自动检测更新:
#!/bin/bash # check-gitlab-update.sh LATEST_TAG=$(curl -s "https://hub.docker.com/v2/repositories/gitlab/gitlab-ce/tags/?page_size=50" | \ jq -r '.results[] | select(.name | test("^[0-9]+\\.[0-9]+\\.[0-9]+-ce\\.0$")) | .name' | \ sort -V | tail -n1) CURRENT_TAG=$(docker inspect gitlab 2>/dev/null | jq -r '.[0].Config.Image' | cut -d: -f2) if [[ "$LATEST_TAG" != "$CURRENT_TAG" ]]; then echo "New version available: $LATEST_TAG (current: $CURRENT_TAG)" # 此处可集成通知或自动拉取逻辑 fi该脚本通过 Docker Hub API 获取所有符合x.y.z-ce.0格式的标签,用sort -V(版本排序)取最新,再与本地容器镜像比对。已在 12 个生产环境稳定运行 18 个月,零误报。
2.3 存储方案设计:为什么必须用命名卷(Named Volume),而非绑定挂载(Bind Mount)?
GitLab 容器内有三类关键数据:
/var/opt/gitlab:存放 Git 仓库、上传附件、CI 缓存、数据库文件(PostgreSQL data directory)/var/log/gitlab:所有组件日志(nginx、gitlab-rails、sidekiq、postgresql)/etc/gitlab:运行时配置(gitlab.rb,由环境变量生成)
初学者常犯的错误是用-v /data/gitlab:/var/opt/gitlab这种绑定挂载。问题在于:容器首次启动时,GitLab 初始化脚本会以root用户身份向/var/opt/gitlab写入大量文件,并设置严格权限(如 PostgreSQL data 目录需0700,owner 为git用户)。而宿主机/data/gitlab目录若由root创建,其 UID/GID 与容器内git用户(UID 1001)不匹配,导致 PostgreSQL 启动失败,报错FATAL: data directory "/var/opt/gitlab/postgresql/data" has wrong ownership。
命名卷(docker volume create gitlab-data)完美解决此问题:Docker 在创建卷时,会自动将卷内目录所有权设为容器内进程的默认 UID。GitLab CE 镜像的git用户 UID 固定为 1001,因此docker volume create创建的卷天然适配。更重要的是,命名卷由 Docker 管理,支持docker volume prune清理、docker volume inspect查看元数据、docker run --volumes-from快速迁移,而绑定挂载需手动chown -R 1001:1001 /data/gitlab,且权限易被其他进程意外修改。
实际部署中,我强制要求使用三个独立命名卷:
gitlab-config:映射/etc/gitlab,存储gitlab.rb配置gitlab-data:映射/var/opt/gitlab,存储核心业务数据gitlab-logs:映射/var/log/gitlab,存储日志便于集中采集
这样设计的好处是:配置、数据、日志三者物理隔离,备份策略可差异化制定——gitlab-config每日 Git Commit 备份,gitlab-data每周全量快照,gitlab-logs按天轮转并推送到 ELK。某物流平台曾因日志卷与数据卷混用,logrotate错误清理了gitlab-data下的repositories目录,导致 37 个仓库丢失。分离后,此类事故归零。
3. 核心配置解析与实操要点
3.1 外部访问地址(external_url)的底层逻辑与常见陷阱
external_url是 GitLab 容器启动时唯一强制要求的环境变量,格式为http://或https://开头的完整 URL。它的作用远不止“告诉 GitLab 网站地址”这么简单——它是整个 GitLab 内部组件通信的根上下文。GitLab Rails 应用、Sidekiq 后台任务、CI Runner、邮件通知模板、API 返回的clone_url,全部基于external_url动态生成。如果配置错误,你会遇到一系列看似无关的故障:邮件里的重置密码链接打不开、CI 任务克隆仓库失败、Web IDE 显示“Repository not found”、API 调用返回 302 重定向到错误域名。
最常见的错误配置是external_url="http://localhost"。这会导致所有生成的 URL 都指向localhost,外部用户无法访问。正确做法是使用可被客户端解析的域名,例如https://gitlab.example.com。但这里有个隐藏前提:该域名必须能被容器内 DNS 解析。GitLab 容器默认使用宿主机 DNS(/etc/resolv.conf),所以确保宿主机/etc/hosts或 DNS 服务器已配置gitlab.example.com解析到宿主机 IP。
更隐蔽的陷阱是 HTTPS 配置。若external_url以https://开头,GitLab 会自动启用 NGINX SSL 终止,并尝试从/etc/gitlab/ssl/目录加载证书。但官方镜像不包含任何证书,因此必须提前将证书文件放入映射的配置卷中。标准流程是:
- 在宿主机创建证书目录:
mkdir -p /srv/gitlab/config/ssl - 将
gitlab.example.com.crt和gitlab.example.com.key放入该目录 - 启动容器时映射:
-v /srv/gitlab/config:/etc/gitlab - 设置
external_url="https://gitlab.example.com"
GitLab 会自动检测/etc/gitlab/ssl/下的证书文件,并配置 NGINX 使用它们。注意证书文件名必须与external_url的域名严格匹配(gitlab.example.com.crt),且私钥无密码保护(否则 NGINX 启动失败)。我推荐使用 Let's Encrypt 的certbot自动生成证书,命令如下:
# 在宿主机上执行(需已安装 certbot) certbot certonly --standalone -d gitlab.example.com \ --non-interactive --agree-tos --email admin@example.com \ --preferred-challenges http # 证书将生成在 /etc/letsencrypt/live/gitlab.example.com/ # 创建软链接到 GitLab 配置目录 ln -sf /etc/letsencrypt/live/gitlab.example.com/fullchain.pem /srv/gitlab/config/ssl/gitlab.example.com.crt ln -sf /etc/letsencrypt/live/gitlab.example.com/privkey.pem /srv/gitlab/config/ssl/gitlab.example.com.key提示:若暂时无法配置 HTTPS,可先用
http://gitlab.example.com启动,待 DNS 和证书就绪后再停机更新external_url并重启。GitLab 支持运行时external_url变更,但需执行gitlab-ctl reconfigure,因此必须通过-v映射配置卷,使修改生效。
3.2 数据库与缓存配置:为什么默认内嵌方案已足够?
GitLab CE 官方镜像内嵌 PostgreSQL 和 Redis,且默认配置已针对中小规模优化。除非你有明确的性能瓶颈证据(如 Sidekiq 队列积压、API 响应超 2s、数据库连接数频繁达上限),否则绝不建议外接数据库。外接会引入网络延迟(跨容器通信比进程间通信慢 3~5 倍)、连接池管理复杂度(需配置gitlab_rails['db_pool']、redis['maxmemory'])、故障域扩大(PostgreSQL 宕机导致整个 GitLab 不可用)。
内嵌 PostgreSQL 的关键参数已预设合理值:
shared_buffers: 256MB(占容器内存 25%,避免 OOM)work_mem: 4MB(平衡排序与哈希操作内存)max_connections: 200(满足 500 用户并发)checkpoint_completion_target: 0.9(平滑写入压力)
这些值位于镜像内的/opt/gitlab/embedded/service/postgresql/config/postgresql.conf,无需手动修改。若需微调,可通过gitlab.rb覆盖,例如:
postgresql['shared_buffers'] = "512MB" postgresql['max_connections'] = 300然后在启动容器时通过-e GITLAB_OMNIBUS_CONFIG注入:
-e GITLAB_OMNIBUS_CONFIG="postgresql['shared_buffers'] = \"512MB\"; postgresql['max_connections'] = 300;"Redis 配置同理,默认maxmemory为 256MB,采用allkeys-lru驱逐策略。Sidekiq 使用 Redis 作为任务队列,其性能瓶颈通常不在 Redis 本身,而在 Sidekiq Worker 数量。GitLab 默认启动 2 个 Worker,对于日均 1000 次 CI 任务的团队,建议提升至 4~6 个:
-e SIDEKIQ_CONCURRENCY=6该环境变量会覆盖gitlab.rb中的sidekiq['concurrency']设置。实测数据显示,Worker 从 2 增至 6,CI 任务平均排队时间从 42s 降至 8s,而 Redis 内存占用仅增加 37MB。
注意:所有数据库和缓存配置变更,都必须通过
GITLAB_OMNIBUS_CONFIG环境变量注入,而非直接修改容器内文件。因为容器文件系统是只读层,重启后修改丢失。环境变量是 GitLab Omnibus 的标准配置入口,确保配置持久化。
3.3 SMTP 邮件服务配置:从测试到生产的平滑过渡
GitLab 的用户注册、密码重置、合并请求通知、CI 状态更新全部依赖邮件。但 SMTP 配置是部署中最易出错的环节之一。我统计过 32 个案例的首次故障原因,SMTP 配置错误占比 41%(13 个),远超 HTTPS(22%)和 DNS(19%)。
官方镜像不预置任何 SMTP 设置,必须通过环境变量显式配置。最小可用配置只需 4 个变量:
-e SMTP_ENABLE=true \ -e SMTP_ADDRESS=smtp.example.com \ -e SMTP_PORT=587 \ -e SMTP_USER_NAME=user@example.com \ -e SMTP_PASSWORD=your_app_password \关键细节在于SMTP_PASSWORD:绝不能使用邮箱登录密码。现代邮箱服务商(Gmail、Outlook、阿里云邮箱)均禁用明文密码登录,必须使用“应用专用密码”(App Password)。以 Gmail 为例:
- 开启 Google 账户的两步验证
- 访问 https://myaccount.google.com/apppasswords
- 选择应用“邮件”,设备“其他”,生成 16 位密码
- 将该密码填入
SMTP_PASSWORD
另一个常见错误是SMTP_PORT选择不当。587是提交端口(Submission),支持 STARTTLS 加密;465是 SMTPS 端口(SSL/TLS),但部分老旧邮件网关不兼容。GitLab 官方推荐587,且需配合SMTP_TLS=true(默认开启)。若使用465,必须设置SMTP_SSL=true,否则连接超时。
生产环境强烈建议启用邮件预览功能,用于调试。在GITLAB_OMNIBUS_CONFIG中添加:
gitlab_rails['smtp_enable'] = true gitlab_rails['smtp_address'] = "smtp.example.com" gitlab_rails['smtp_port'] = 587 gitlab_rails['smtp_user_name'] = "user@example.com" gitlab_rails['smtp_password'] = "app_password" gitlab_rails['smtp_domain'] = "example.com" gitlab_rails['smtp_authentication'] = "login" gitlab_rails['smtp_enable_starttls_auto'] = true gitlab_rails['gitlab_email_from'] = "gitlab@example.com" gitlab_rails['gitlab_email_reply_to'] = "noreply@example.com"然后启动容器后,访问https://gitlab.example.com/admin/logs,点击 “Emails” 标签页,可查看所有发送日志及错误详情。某电商客户曾因smtp_domain未设置,导致 Gmail 将邮件标记为垃圾邮件,开启日志后 5 分钟定位到问题。
4. 完整实操流程与关键环节实现
4.1 环境准备:操作系统、Docker、资源规划
GitLab 容器对宿主机有明确要求。我推荐的最小可行配置是:
- 操作系统:Ubuntu 22.04 LTS 或 CentOS 7.9+(内核 ≥ 3.10,支持 overlay2 存储驱动)
- Docker 版本:≥ 20.10(支持
--cgroup-parent控制资源,避免 OOM Killer 误杀) - CPU:4 核(GitLab Rails、Sidekiq、PostgreSQL、NGINX 四进程并发)
- 内存:8GB(PostgreSQL 占 2.5GB,Rails 占 2GB,Sidekiq 占 1.5GB,NGINX 占 0.5GB,余量应对峰值)
- 磁盘:50GB SSD(
/var/opt/gitlab占用随仓库数量线性增长,单仓平均 200MB)
部署前必须执行三项检查:
确认 Docker 存储驱动:
docker info | grep "Storage Driver",输出必须为overlay2。若为aufs或devicemapper,需修改/etc/docker/daemon.json:{ "storage-driver": "overlay2" }然后
sudo systemctl restart docker。关闭 swap:GitLab 内存敏感,swap 会引发 PostgreSQL 性能断崖。执行
sudo swapoff -a,并注释/etc/fstab中 swap 行。调整内核参数:GitLab 需要大量文件描述符和进程数。编辑
/etc/sysctl.conf:fs.file-max = 2097152 vm.swappiness = 0 net.core.somaxconn = 1024执行
sudo sysctl -p生效。
实操心得:我曾在一台 2 核 4GB 的测试机上强行部署 GitLab,结果 Sidekiq Worker 频繁 OOM 被 kill,CI 任务排队超 10 分钟。加到 4 核 8GB 后,相同负载下 CPU 利用率稳定在 45%,无排队。资源不是越省越好,而是要为 GitLab 的多进程模型留足余量。
4.2 创建命名卷与目录结构
按前文设计,创建三个命名卷:
docker volume create gitlab-config docker volume create gitlab-data docker volume create gitlab-logs验证创建成功:
$ docker volume ls | grep gitlab local gitlab-config local gitlab-data local gitlab-logs为便于管理,我在宿主机/srv/gitlab/下建立符号链接,指向 Docker 卷的实际路径:
# 获取卷实际路径(以 gitlab-config 为例) VOLUME_PATH=$(docker volume inspect gitlab-config | jq -r '.[0].Mountpoint') sudo ln -sf "$VOLUME_PATH" /srv/gitlab/config # 同理处理 data 和 logs VOLUME_PATH=$(docker volume inspect gitlab-data | jq -r '.[0].Mountpoint') sudo ln -sf "$VOLUME_PATH" /srv/gitlab/data VOLUME_PATH=$(docker volume inspect gitlab-logs | jq -r '.[0].Mountpoint') sudo ln -sf "$VOLUME_PATH" /srv/gitlab/logs这样做的好处是:备份脚本可直接操作/srv/gitlab/目录,无需调用docker volume inspect;日志采集工具(如 Filebeat)可固定监控/srv/gitlab/logs;新成员入职时,ls /srv/gitlab/即知数据位置。
4.3 启动容器:一行命令背后的 17 个关键参数
最终启动命令不是简单的docker run,而是经过 17 次迭代优化的生产级配置。以下是我在某物联网公司落地的最终版本(已脱敏):
docker run -d \ --hostname gitlab.example.com \ --publish 443:443 --publish 80:80 --publish 22:22 \ --name gitlab \ --restart always \ --volume gitlab-config:/etc/gitlab \ --volume gitlab-data:/var/opt/gitlab \ --volume gitlab-logs:/var/log/gitlab \ --shm-size 256m \ --memory 6g \ --cpus 3.5 \ --ulimit nofile=30000:30000 \ --ulimit nproc=10000:10000 \ -e TZ=Asia/Shanghai \ -e GITLAB_OMNIBUS_CONFIG="external_url 'https://gitlab.example.com'; gitlab_rails['time_zone'] = 'Beijing'; gitlab_rails['gitlab_shell_ssh_port'] = 22; nginx['redirect_http_to_https'] = true; nginx['ssl_certificate'] = '/etc/gitlab/ssl/gitlab.example.com.crt'; nginx['ssl_certificate_key'] = '/etc/gitlab/ssl/gitlab.example.com.key'; gitlab_rails['smtp_enable'] = true; gitlab_rails['smtp_address'] = 'smtp.exmail.qq.com'; gitlab_rails['smtp_port'] = 587; gitlab_rails['smtp_user_name'] = 'gitlab@example.com'; gitlab_rails['smtp_password'] = 'app_password_123'; gitlab_rails['smtp_domain'] = 'example.com'; gitlab_rails['smtp_authentication'] = 'login'; gitlab_rails['smtp_enable_starttls_auto'] = true; gitlab_rails['gitlab_email_from'] = 'gitlab@example.com'; sidekiq['concurrency'] = 6;" \ -e SMTP_ENABLE=true \ -e SMTP_ADDRESS=smtp.exmail.qq.com \ -e SMTP_PORT=587 \ -e SMTP_USER_NAME=gitlab@example.com \ -e SMTP_PASSWORD=app_password_123 \ gitlab/gitlab-ce:16.11.5-ce.0逐项解释关键参数:
--hostname gitlab.example.com:设置容器 hostname,影响内部服务解析(如 Gitaly 通信)--publish 443:443 --publish 80:80 --publish 22:22:映射 HTTPS、HTTP、SSH 端口。注意:若宿主机 22 端口已被 SSH 服务占用,可改为--publish 2222:22,并在GITLAB_OMNIBUS_CONFIG中设置gitlab_rails['gitlab_shell_ssh_port'] = 2222--shm-size 256m:分配共享内存,避免 PostgreSQLfork()失败(默认 64MB 不足)--memory 6g --cpus 3.5:限制容器资源,防止抢占宿主机其他服务--ulimit nofile=30000:30000:提高文件描述符上限,应对高并发 Git 操作TZ=Asia/Shanghai:设置时区,确保日志时间戳与本地一致GITLAB_OMNIBUS_CONFIG:核心配置,用分号分隔多条 Ruby 语句。注意字符串内单引号需转义为\'
启动后,执行docker logs -f gitlab观察初始化日志。正常流程是:Starting Chef Client...→Compiling Cookbooks...→Recipe: gitlab::setup→Running handlers...→Chef Client finished。整个过程约 3~5 分钟。当看到gitlab Reconfigured!字样,即可访问https://gitlab.example.com。
实操心得:第一次启动务必用
docker logs -f跟踪,不要急于浏览器访问。我见过太多人因等待超时而反复重启容器,结果 PostgreSQL 初始化被中断,导致数据目录损坏。GitLab 初始化是幂等的,但中断后需手动清理/var/opt/gitlab/postgresql/data并重启,耗时更长。
4.4 首次访问与管理员账户初始化
容器启动成功后,首次访问https://gitlab.example.com会进入初始化页面。此时 GitLab 正在后台执行数据库迁移(gitlab-ctl migrate),页面显示 “Setting up GitLab for the first time... This may take a few minutes.”。切勿刷新或关闭页面,否则可能中断迁移。
初始化完成后,页面跳转至管理员密码设置页。输入 8 位以上密码(含大小写字母、数字),点击 “Change your password”。系统自动创建用户名为root的超级管理员账户。
登录后,立即执行三件事:
- 修改 root 密码:进入
Admin Area > Overview > Users,找到root用户,点击编辑图标,重置为更安全的密码。 - 创建普通用户:点击右上角
+→New user,填写姓名、邮箱、用户名,勾选Skip confirmation email(测试环境),点击Create user。 - 验证邮件功能:进入
Admin Area > Monitoring > Logs,点击 “Emails”,发送测试邮件(Admin Area > Settings > General > Email下的 “Test email settings” 按钮)。
注意:若测试邮件失败,不要慌。先检查
docker logs gitlab | grep smtp是否有连接拒绝日志;再确认gitlab.example.com的 MX 记录是否正确(dig mx example.com);最后检查防火墙是否放行 587 端口(sudo ufw status)。
5. 常见问题与排查技巧实录
5.1 启动失败:容器退出,日志显示 “Failed to connect to PostgreSQL”
这是最高频问题,占所有启动失败的 68%。典型日志片段:
Error connecting to the database: FATAL: database "gitlabhq_production" does not exist ... Failed to connect to PostgreSQL after 10 attempts. Exiting.根本原因不是数据库不存在,而是 PostgreSQL 服务未就绪。GitLab 初始化脚本会循环尝试连接,但默认超时时间为 300 秒(5 分钟)。若宿主机磁盘 I/O 慢(如机械硬盘),或内存不足触发 OOM Killer 杀死 PostgreSQL 进程,就会超时退出。
排查步骤:
- 查看 PostgreSQL 日志:
docker exec -it gitlab cat /var/log/gitlab/postgresql/current - 若日志为空,说明 PostgreSQL 未启动。执行
docker exec -it gitlab gitlab-ctl status postgresql,检查状态。 - 若状态为
down,手动启动:docker exec -it gitlab gitlab-ctl start postgresql - 若启动失败,检查磁盘空间:
docker exec -it gitlab df -h /var/opt/gitlab,确保/var/opt/gitlab所在分区剩余空间 > 1GB。 - 若空间充足,检查内存:
docker exec -it gitlab free -h,确认可用内存 > 2GB。
终极解决方案:延长超时时间。在GITLAB_OMNIBUS_CONFIG中添加:
postgresql['listen_address'] = '127.0.0.1' postgresql['port'] = 5432 postgresql['max_connections'] = 200 # 延长数据库连接超时 gitlab_rails['db_connect_timeout'] = 30 gitlab_rails['db_pool'] = 25并确保容器启动时添加--memory 6g限制,避免内存争抢。
5.2 访问 502 Bad Gateway:NGINX 无法连接上游服务
现象:浏览器打开https://gitlab.example.com显示 “502 Bad Gateway”,但容器状态为Up。日志中出现:
2023-10-05_08:12:34.56789 connect() failed (111: Connection refused) while connecting to upstream这表示 NGINX 作为反向代理,无法连接到上游的 GitLab Rails 应用(Unicorn/Puma)。常见原因有三:
- Rails 进程未启动:执行
docker exec -it gitlab gitlab-ctl status unicorn,若为down,则gitlab-ctl start unicorn。 - 端口冲突:Rails 默认监听
127.0.0.1:8080,若被其他进程占用,需修改unicorn['port'] = 8081。 - SELinux 限制(仅 CentOS/RHEL):执行
sudo setsebool -P httpd_can_network_connect 1,允许 NGINX 连接网络。
快速诊断命令:
# 检查 NGINX 是否运行