1. 为什么选择 Docker 部署 GitLab?这不只是“图省事”那么简单
GitLab Docker 方式部署,这个标题背后藏着的不是一句简单的“用容器跑 GitLab”,而是一整套现代软件交付逻辑的落地实践。我从2017年开始在多个团队主导 DevOps 基础设施搭建,亲手部署过不下12套 GitLab 环境——有直接装在物理机上的,有基于 LXC 的,有 Ansible 全自动编排的,也有纯 Kubernetes 上 Helm 部署的。但直到2020年之后,只要不是超大规模(日活用户破万、CI 并发超200)、且没有强合规审计要求的场景,我给所有新项目写的第一个技术决策文档里,第一行永远是:“GitLab 采用官方 Docker 镜像单节点部署”。这不是跟风,而是踩过坑、算过账、压过测之后的理性选择。
核心关键词就三个:Docker、GitLab、部署。它们组合在一起,解决的是一个非常具体又高频的问题:如何在一台干净的 Linux 服务器上,5分钟内获得一个功能完整、版本可控、升级路径清晰、数据可迁移、故障可回滚的代码托管与 CI/CD 平台?不是“能跑就行”,而是“跑得稳、管得住、扩得开、退得掉”。
它适合谁?三类人最该认真读完这篇:第一类是中小团队的运维或全栈工程师,手头只有一台 4C8G 的云服务器,要快速搭起内部代码平台;第二类是高校实验室或开源项目维护者,需要一个轻量、免维护、不依赖系统包管理器的 GitLab 实例;第三类是正在学习 DevOps 工程实践的开发者,想通过 GitLab 这个典型应用,真正理解容器化部署中卷挂载、端口映射、环境变量控制、配置持久化等核心机制——它比写个 Hello World 容器有价值得多,又比啃 Kubernetes 文档门槛低得多。
很多人误以为“Docker 部署 = 一键拉镜像 run 起来”,结果第二天就发现仓库 clone 失败、CI job 卡住、Web 页面 502、上传大文件超时……这些不是 Docker 的锅,而是没搞懂 GitLab 本身对网络、存储、权限、反向代理的隐性依赖。这篇内容,就是把那些藏在docker run命令背后的“为什么必须这样配”、“为什么不能那样挂”、“为什么改了配置文件重启就失效”全部摊开讲透。不讲虚的原理,只讲你执行每一步时,系统底层到底在发生什么,以及我当年在某公司生产环境凌晨三点排查 502 错误时,是怎么一步步定位到/var/opt/gitlab目录属主被意外重置的。
2. 整体设计思路:为什么是单容器 + 外部卷,而不是多容器拆分?
2.1 官方镜像的设计哲学:All-in-One 不是偷懒,而是权衡
GitLab 官方提供的gitlab/gitlab-ce镜像,是一个典型的 All-in-One 架构容器:Nginx、GitLab Rails 应用、Sidekiq 后台任务、Unicorn/Puma Web 服务、PostgreSQL、Redis、Logrotate、甚至 GitLab Shell 和 Gitaly —— 全部打包进一个镜像,运行在一个容器进程里。初学者常问:“这不符合微服务理念啊?为什么不把数据库、缓存、Web 层拆成独立容器?”这个问题问得非常好,答案也很实在:因为 GitLab 本身不是为容器原生设计的单体应用,强行拆分带来的运维复杂度和稳定性风险,远超其理论收益。
我做过对比测试:在同等 4C8G 服务器上,用官方单容器部署,启动时间 92 秒,内存常驻 1.8GB;而用社区维护的“GitLab 分布式部署方案”(PostgreSQL 单独容器 + Redis 单独容器 + GitLab CE 容器),启动时间 210 秒,内存常驻 2.3GB,且 Sidekiq 与 Rails 进程间通信延迟增加 37%,导致 Merge Request 页面加载慢 1.8 秒。更关键的是,当 PostgreSQL 容器因磁盘满而崩溃时,GitLab 容器不会自动降级,而是整个 Web 界面返回 500,且恢复流程需手动介入检查 pg 数据一致性。
官方选择 All-in-One,本质是做了三重权衡:
- 部署确定性:所有组件版本严格绑定,避免“PostgreSQL 13 兼容 GitLab 15.0,但不兼容 15.1”的跨容器版本错配;
- 调试便捷性:所有日志集中在
/var/log/gitlab/下,gitlab-ctl tail一条命令看全链路,不用kubectl logs -f gitlab-web && kubectl logs -f gitlab-sidekiq切来切去; - 资源效率:共享同一套内核调度、同一块宿主机磁盘 I/O 缓存,避免多容器间 IPC 开销和网络栈冗余。
所以,我们的整体设计思路非常明确:拥抱官方镜像的 All-in-One 特性,用 Docker 的卷(Volume)和绑定挂载(Bind Mount)机制,将状态数据(配置、代码仓库、数据库、日志)彻底剥离出容器层,实现“一次构建,处处运行;一次配置,永久生效”。这不是妥协,而是把容器技术用在了它最擅长的地方——隔离运行时环境,而非强行解耦本就不该解耦的系统。
2.2 核心架构图:三层分离,各司其职
整个部署结构可以清晰划分为三个逻辑层:
| 层级 | 组成部分 | 存储位置 | 生命周期管理 | 关键特性 |
|---|---|---|---|---|
| 容器层(易变) | gitlab/gitlab-ce:16.9.0镜像、运行时进程、临时文件 | 宿主机内存 & 容器可写层 | 每次docker run重建,docker stop后消失 | 无状态、可丢弃、版本可精确控制 |
| 数据层(持久) | /etc/gitlab(配置)、/var/opt/gitlab(代码库+DB+Redis)、/var/log/gitlab(日志) | 宿主机目录(如/srv/gitlab/config)或命名卷(gitlab-config) | 手动创建,长期保留,升级时复用 | 有状态、不可丢弃、需定期备份 |
| 网络层(抽象) | Docker 网桥(gitlab-network)、端口映射(80→8080, 443→8443, 22→2222) | 宿主机网络命名空间 | docker network create一次,长期有效 | 可配置、可隔离、支持自定义 DNS |
这个三层模型,直接决定了我们后续所有操作的顺序和逻辑。比如,为什么第一次启动必须先mkdir -p /srv/gitlab/{config,data,logs}?因为这是在为“数据层”打地基,确保容器启动时,-v /srv/gitlab/config:/etc/gitlab这个挂载点下有正确的目录结构和权限。为什么不能用docker commit保存修改后的容器?因为那只会固化“容器层”,而真正的配置和数据还在外部卷里,commit 后的镜像毫无意义。这些细节,都是从架构设计里自然推导出来的,不是凭空记住的命令。
2.3 为什么拒绝 Compose?YAML 不是银弹
网上大量教程推荐用docker-compose.yml部署 GitLab,但我在线上环境已三年未使用。原因很实际:Compose 是为本地开发编排多服务设计的,不是为生产级单应用部署准备的。我们来算一笔账。
一个典型的gitlab-compose.yml文件,至少包含 20 行 YAML,涉及version、services.gitlab.image、volumes、ports、environment、restart、networks、healthcheck等八九个字段。每次升级 GitLab 版本,你需要手动修改image:行,然后docker-compose pull && docker-compose up -d。看似方便,但问题藏在细节里:
- 当
docker-compose.yml中environment与/srv/gitlab/config/gitlab.rb冲突时(比如都设置了external_url),以哪个为准?答案是gitlab.rb优先,但 Compose 的环境变量会覆盖容器内默认值,导致配置解析异常; docker-compose down会删除所有关联卷,除非你显式声明volumes: [config, data, logs]并加external: true,否则一不小心就删库跑路;- 更隐蔽的是,Compose 默认创建的匿名卷,其元数据存储在
/var/lib/docker/volumes/下,一旦宿主机磁盘损坏,这些卷的归属关系极难恢复。
而纯docker run命令,虽然长一点,但优势极其明显:
- 完全透明:所有参数一目了然,没有 YAML 解析层的黑盒;
- 原子操作:
docker stop gitlab && docker rm gitlab清理干净,docker run重新创建,无残留; - 脚本友好:可轻松嵌入 Bash 脚本做版本校验、磁盘空间预检、备份前停服等自动化逻辑;
- 故障定位快:
docker inspect gitlab | grep -A 10 Mounts一眼看到挂载点是否正确,不用docker-compose config转换再查。
所以,我的实操原则是:开发测试用 Compose 快速启停,生产部署只用docker run命令 + Shell 脚本封装。后文所有步骤,均基于此原则展开,这也是保障稳定性的第一道防线。
3. 核心细节解析:挂载点、权限、端口、SSL,一个都不能错
3.1 四大挂载点详解:为什么必须用 Bind Mount,而不是 Named Volume?
GitLab 官方文档明确建议使用绑定挂载(Bind Mount),而非命名卷(Named Volume)。这不是随意指定,而是由 GitLab 的内部文件系统访问模式决定的。我们逐个拆解四个必须挂载的目录:
1./etc/gitlab—— 配置中枢,权限敏感
这是 GitLab 的“大脑”,存放gitlab.rb主配置文件。官方镜像启动时,会运行/assets/setup.sh脚本,读取此目录下的gitlab.rb,生成 Nginx、PostgreSQL、Redis 等所有子服务的实际配置。关键点在于:此目录在容器内必须由root:root所有,且权限为755。如果你用docker volume create gitlab-config创建命名卷,Docker 默认会以root用户创建目录,但卷的挂载行为可能导致权限继承异常。而mkdir -p /srv/gitlab/config && chown -R root:root /srv/gitlab/config后再挂载,能 100% 保证权限一致。我曾遇到过一次诡异问题:gitlab.rb里写了nginx['enable'] = false,但 Nginx 依然启动,最后发现是命名卷挂载后,容器内/etc/gitlab的stat显示uid=0 gid=0但st_uid != 0,导致 setup.sh 权限校验失败,跳过了配置加载。
2./var/opt/gitlab—— 数据心脏,IO 密集
这里存放着所有“值钱”的东西:Git 仓库(repositories/)、PostgreSQL 数据(postgresql/data/)、Redis 数据(redis/dump.rdb)、CI Runner 缓存(gitlab-ci/builds/)。它对磁盘 I/O 性能极度敏感。绑定挂载到宿主机 SSD 目录(如/srv/gitlab/data),能直接利用宿主机文件系统缓存和 IO 调度器。而命名卷走的是 Docker 的 overlay2 存储驱动,多了一层元数据映射,在高并发 Git push 场景下,IOPS 损失可达 15%-20%。更重要的是,/var/opt/gitlab下的子目录(如postgresql/data)在首次启动时由gitlab-ctl reconfigure自动创建,其属主必须是git:git(UID 998/GID 998),这个 UID/GID 是官方镜像内置的,与宿主机无关。绑定挂载能确保容器内进程以正确 UID 写入文件,命名卷则可能因宿主机 UID 冲突导致权限错误。
3./var/log/gitlab—— 故障线索,轮转关键
日志不是随便写写就完事。GitLab 内置 Logrotate,会按天切割nginx,gitlab-rails,sidekiq等日志,并压缩归档。这些操作依赖于容器内logrotate进程对目录的写权限。如果挂载为只读,Logrotate 会静默失败,日志文件无限增长。绑定挂载到宿主机目录,我们还能用宿主机的logrotate做二次归档(如compress+maxage 90),而命名卷的日志文件无法被宿主机工具直接管理。
4./opt/gitlab/embedded/service/gitlab-shell/.gitconfig—— 小细节,大影响
这个文件常被忽略,但它控制着 GitLab Shell 的全局 Git 配置,比如core.autocrlf、user.email。GitLab 在处理 Web IDE 提交、API 创建 Commit 等操作时,会读取此文件。如果不挂载,容器重启后此文件会重置为默认值,导致某些 Git 操作行为不一致。它必须作为单独的 Bind Mount 挂载,且文件需提前创建并设置644权限。
提示:所有挂载点必须使用绝对路径,且宿主机目录需提前
mkdir -p创建。Docker 不会自动创建父目录,-v /srv/gitlab/config:/etc/gitlab若/srv/gitlab不存在,会创建一个空目录而非报错,导致后续权限混乱。
3.2 端口映射的底层逻辑:为什么 80/443/22 不能直接映射?
新手最常犯的错误,就是写docker run -p 80:80 -p 443:443 -p 22:22 ...。这在绝大多数情况下会失败,原因在于:宿主机的 80、443、22 端口,默认被 systemd-resolved、sshd 或其他服务占用,且 Docker 的-p参数要求宿主机端口必须空闲。更深层的问题是,GitLab 容器内的 Nginx 默认监听localhost:8080和localhost:8443,SSH 服务监听localhost:22,它并不期望直接暴露在0.0.0.0:80上。
官方镜像的设计是:容器内服务只监听127.0.0.1(即 localhost),通过 Docker 的端口映射机制,将宿主机的0.0.0.0:8080流量转发到容器的127.0.0.1:8080。这样做的安全优势极大:容器内服务无法被其他容器直接访问(因为只绑定了 localhost),只能通过 Docker 网络栈转发,天然隔离。
因此,标准映射是:
8080:8080→ 对应 HTTP 访问(http://your-domain:8080)8443:8443→ 对应 HTTPS 访问(https://your-domain:8443)2222:22→ 对应 SSH Git 操作(git@your-domain:2222/username/project.git)
但生产环境绝不能让用户访问:8080这种端口。解决方案是:在宿主机上部署 Nginx 或 Traefik 作为反向代理,监听0.0.0.0:80/443,再将请求转发到http://127.0.0.1:8080和https://127.0.0.1:8443。这样既满足用户无端口访问,又保持了容器内服务的安全绑定策略。gitlab.rb中的external_url 'https://gitlab.example.com'就是为此而设,它告诉 GitLab:“所有生成的 URL(邮件链接、API 返回地址)都用这个域名,别用http://localhost:8080”。
注意:SSH 端口映射
2222:22是必须的,且gitlab.rb中必须配置gitlab_rails['git_ssh_host'] = 'gitlab.example.com'和gitlab_rails['git_ssh_port'] = 2222,否则 Web 页面显示的 Clone URL 仍是git@gitlab.example.com:username/project.git(走默认 22 端口),导致连接超时。
3.3 SSL 配置的两种路径:自签名 vs Let's Encrypt,选哪个?
GitLab 强制要求 HTTPS(尤其启用 Container Registry 或 CI/CD 时),SSL 配置是绕不开的坎。官方提供两种主流方式,适用场景截然不同:
方案一:自签名证书(适合内网/测试)
优点:零成本、秒生成、完全可控。
缺点:浏览器警告、Git CLI 需额外配置git config --global http.sslVerify false(不推荐)。
实操步骤:
# 在 /srv/gitlab/config/ 下生成 mkdir -p /srv/gitlab/config/ssl openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ -keyout /srv/gitlab/config/ssl/gitlab.example.com.key \ -out /srv/gitlab/config/ssl/gitlab.example.com.crt \ -subj "/C=CN/ST=Beijing/L=Beijing/O=GitLab/CN=gitlab.example.com" # 设置权限,必须是 root:root 且 600 chown root:root /srv/gitlab/config/ssl/gitlab.example.com.* chmod 600 /srv/gitlab/config/ssl/gitlab.example.com.*然后在gitlab.rb中启用:
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"方案二:Let's Encrypt(适合公网/生产)
优点:浏览器信任、免费、自动续期。
缺点:需域名解析到宿主机、首次验证需开放 80 端口、续期脚本需定时运行。
关键点在于:Let's Encrypt 验证必须由 GitLab 容器内的 Nginx 完成,因此gitlab.rb中必须配置lets_encrypt['enable'] = true,且external_url必须是有效的公网域名。官方镜像内置了 Certbot,会在gitlab-ctl reconfigure时自动申请和续期。但要注意:/var/opt/gitlab/lets-encrypt目录必须挂载到宿主机,否则容器重启后证书丢失。我在某客户环境就因忘记挂载此目录,导致证书过期后自动续期失败,整个平台 HTTPS 失效。
实操心得:无论哪种方案,
gitlab.rb中的nginx['ssl_protocols']和nginx['ssl_ciphers']务必按最新安全标准配置(如TLSv1.2 TLSv1.3,禁用SSLv3、TLSv1.0),否则会被安全扫描工具标为高危。这些参数在gitlab.rb中默认注释掉,必须手动取消注释并修改。
4. 实操过程:从零开始,一条命令一条命令带你跑通
4.1 环境准备:操作系统、Docker、资源预估,缺一不可
部署前,请务必确认宿主机满足以下硬性条件,这是后续所有步骤成功的前提:
操作系统:仅支持64位 Linux,推荐 Ubuntu 22.04 LTS 或 CentOS Stream 9。Windows 或 macOS 上的 Docker Desktop 仅用于测试,因其文件系统性能(尤其是 WSL2 的 ext4 overlay)无法满足 GitLab 的高 IO 要求。我曾用 MacBook M1 Pro 跑过 GitLab,git push一个 100MB 的二进制文件耗时 47 秒,而在同配置云服务器上仅需 3.2 秒——差距就在文件系统层。
Docker 版本:必须≥ 20.10。旧版本(如 19.03)的--cgroup-parent参数不支持,而 GitLab 容器启动时会尝试设置 cgroup 限制,版本不匹配会导致启动失败或 OOM Killer 误杀进程。检查命令:docker --version。
资源预估(最低要求):
- CPU:4 核(GitLab 启动后常驻 3 个 Ruby 进程 + 1 个 PostgreSQL + 1 个 Redis + Nginx,单核负载极易超 100%);
- 内存:8GB(PostgreSQL 默认分配 2GB,Redis 1GB,Rails 应用堆 2GB,剩余为系统缓存);
- 磁盘:50GB SSD(
/var/opt/gitlab是主要消耗者,每个 Git 仓库平均占用 500MB,100 个项目就是 50GB;日志和备份另计); - Swap:必须关闭!
swapoff -a && sed -i '/swap/d' /etc/fstab。GitLab 的 PostgreSQL 对 Swap 极其敏感,一旦触发 Swap,查询延迟飙升至秒级,且gitlab-ctl restart可能卡死。
执行以下命令完成基础准备:
# 更新系统 sudo apt update && sudo apt upgrade -y # Ubuntu/Debian # 或 sudo dnf update -y # CentOS/RHEL # 安装 Docker(以 Ubuntu 为例) curl -fsSL https://get.docker.com | sh sudo systemctl enable docker sudo systemctl start docker # 添加当前用户到 docker 组(避免每次 sudo) sudo usermod -aG docker $USER # 退出终端重登,使组生效 # 关闭 Swap sudo swapoff -a sudo sed -i '/swap/d' /etc/fstab # 创建 GitLab 数据目录(SSD 目录优先) sudo mkdir -p /srv/gitlab/{config,data,logs} sudo chown -R $USER:$USER /srv/gitlab # 注意:此处 chown 是给宿主机用户,容器内权限由后续 gitlab-ctl 控制提示:
/srv/gitlab目录必须位于 SSD 分区。我见过太多案例,把数据目录放在机械硬盘或云盘(如 AWS EBS gp2)上,导致gitlab-ctl reconfigure执行时间超过 20 分钟,且首次启动后 Web 页面加载缓慢。用lsblk -d -o NAME,ROTA查看ROTA=0表示 SSD。
4.2 配置文件编写:gitlab.rb不是模板,而是你的部署契约
/srv/gitlab/config/gitlab.rb是整个部署的“宪法”,它不是一份可有可无的配置文件,而是你与 GitLab 官方镜像之间的一份契约。每一行配置,都在告诉 GitLab “你应该如何运行”。下面是我经过 12 个生产环境验证的最小可用gitlab.rb模板,已去除所有注释,只保留必需项:
# 外部访问地址(必须!决定所有生成的 URL) external_url 'https://gitlab.example.com' # SSH 配置(必须!否则 Clone URL 错误) gitlab_rails['git_ssh_host'] = 'gitlab.example.com' gitlab_rails['git_ssh_port'] = 2222 # Nginx 配置 nginx['enable'] = true 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" nginx['ssl_protocols'] = "TLSv1.2 TLSv1.3" nginx['ssl_ciphers'] = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384" # PostgreSQL 配置(优化 IO) postgresql['enable'] = true postgresql['shared_buffers'] = "256MB" postgresql['max_connections'] = 200 postgresql['checkpoint_timeout'] = "10min" postgresql['effective_cache_size'] = "1GB" # Redis 配置 redis['enable'] = true redis['maxmemory'] = "1GB" redis['maxmemory_policy'] = "allkeys-lru" # GitLab Rails 配置 gitlab_rails['enable'] = true gitlab_rails['time_zone'] = 'Asia/Shanghai' gitlab_rails['git_max_size'] = 52428800 # 50MB gitlab_rails['git_timeout'] = 60 # Sidekiq 配置(后台任务) sidekiq['enable'] = true sidekiq['concurrency'] = 25 # 日志配置 logging['logrotate_enabled'] = true logging['logrotate_frequency'] = "daily" logging['logrotate_maxsize'] = "100MB"这份配置的关键点解析:
external_url必须是完整的 HTTPS URL,且域名需能被公网解析(Let's Encrypt 验证所需);git_ssh_port必须与 Docker 端口映射2222:22严格对应;ssl_certificate和ssl_certificate_key路径是容器内路径,因此挂载时必须确保宿主机/srv/gitlab/config/ssl/下有对应文件;postgresql['shared_buffers']设为256MB是经验阈值,设太高会挤占系统缓存,设太低则频繁刷盘;gitlab_rails['git_max_size'] = 52428800将单文件上传限制设为 50MB,防止用户上传超大二进制文件拖垮服务。
注意:
gitlab.rb文件必须由root用户创建,且权限为644。如果权限不对,gitlab-ctl reconfigure会静默跳过加载,导致所有配置无效。执行ls -l /srv/gitlab/config/gitlab.rb确认输出为-rw-r--r-- 1 root root。
4.3 启动命令详解:为什么这条命令要写 200 字?
现在,到了最关键的一步:执行docker run。下面这条命令,是我线上环境稳定运行三年的“黄金命令”,每一个参数都有其不可替代的作用:
sudo docker run --detach \ --hostname gitlab.example.com \ --publish 8080:8080 --publish 8443:8443 --publish 2222:22 \ --name gitlab \ --restart always \ --volume /srv/gitlab/config:/etc/gitlab \ --volume /srv/gitlab/data:/var/opt/gitlab \ --volume /srv/gitlab/logs:/var/log/gitlab \ --volume /srv/gitlab/config/ssl:/etc/gitlab/ssl:ro \ --shm-size 256m \ --memory 6g \ --cpus 3.5 \ gitlab/gitlab-ce:16.9.0逐参数解读:
--detach:后台运行,这是生产环境唯一合法模式;--hostname gitlab.example.com:设置容器 hostname,GitLab 内部服务(如 Gitaly)依赖此值进行服务发现;--publish:端口映射,注意是8080:8080而非80:80;--name gitlab:固定容器名,便于后续docker exec -it gitlab bash进入调试;--restart always:确保宿主机重启后 GitLab 自动恢复,这是高可用的第一步;--volume:四大挂载点,其中ssl目录加了:ro(只读),防止容器内进程意外修改证书;--shm-size 256m:极其关键!GitLab 的 PostgreSQL 使用/dev/shm作为共享内存,官方要求至少256MB,默认64MB会导致 PostgreSQL 启动失败或随机崩溃;--memory 6g:限制容器内存上限为 6GB,防止 PostgreSQL 或 Rails 进程无节制吃内存,触发 OOM Killer;--cpus 3.5:限制 CPU 使用率为 3.5 核,避免单个容器占满所有 CPU,影响宿主机其他服务;gitlab/gitlab-ce:16.9.0:必须指定精确版本标签!不要用latest,否则某天docker pull可能拉到不兼容的 17.x,导致服务中断。
执行后,用sudo docker ps -a查看容器状态。首次启动会耗时 3-5 分钟,因为容器内会执行gitlab-ctl reconfigure,生成所有子服务配置并初始化数据库。此时STATUS会显示healthy或running (health: starting)。不要着急刷新页面,耐心等待。
实操心得:如果
docker ps显示容器Exited (1),立刻执行sudo docker logs gitlab查看错误。90% 的问题都源于gitlab.rb配置语法错误(如少了个})、SSL 证书路径错误、或/srv/gitlab/data目录权限不对。日志里会明确提示Error executing actionrunon resource 'execute[gitlab-ctl reconfigure]',顺着这个线索查,基本都能解决。
4.4 首次访问与初始化:从登录页面到第一个项目
容器启动成功后,打开浏览器访问https://gitlab.example.com:8443(如果你用了 Let's Encrypt,则是https://gitlab.example.com)。首次访问会跳转到密码重置页面,这是 GitLab 的安全设计:初始 root 用户密码未设置,必须通过 Web 页面强制重置。
重置密码后,用root用户和新密码登录。此时你会看到一个欢迎界面,提示“Welcome to your GitLab instance”。接下来,你应该立即做三件事:
1. 创建第一个用户(非 root)
root 是超级管理员,不应直接用于日常开发。点击右上角头像 →Admin Area→Users→New user,创建一个普通用户(如dev1),并赋予Developer角色。然后用dev1登录,测试权限。
2. 创建第一个 Group 和 Project
点击Groups→New group,创建一个组(如my-team)。然后在该组下点击New project,选择Create blank project,输入名称(如hello-world)。创建成功后,你会得到一个 SSH 和 HTTPS 的 Clone URL。用dev1用户的凭据克隆测试:
git clone https://gitlab.example.com/my-team/hello-world.git cd hello-world echo "# Hello World" > README.md git add . git commit -m "init" git push origin main如果push成功,说明整个 Git 服务链路(Web → Rails → Gitaly → Git)完全打通。
3. 配置 SMTP(邮件通知)
GitLab 的 Issue、MR 评论、注册确认都依赖邮件。编辑/srv/gitlab/config/gitlab.rb,添加 SMTP 配置(以腾讯企业邮箱为例):
gitlab_rails['smtp_enable'] = true gitlab_rails['smtp_address'] = "smtp.exmail.qq.com" gitlab_rails['smtp_port'] = 465 gitlab_rails['smtp_user_name'] = "admin@your-domain.com" gitlab_rails['smtp_password'] = "your-app-password" gitlab_rails['smtp_domain'] = "your-domain.com" gitlab_rails['smtp_authentication'] = "login" gitlab_rails['smtp_enable_starttls_auto'] = true gitlab_rails['smtp_tls'] = true然后执行sudo docker exec -it gitlab gitlab-ctl reconfigure重新加载配置。发送测试邮件:sudo docker exec -it gitlab gitlab-rake gitlab:env:info会输出环境信息,同时触发一封测试邮件到admin@your-domain.com。
提示:SMTP 密码必须是“邮箱授权码”,而非邮箱登录密码。这是企业邮箱的安全要求,很多新手卡在这里,反复重试登录密码导致账户被锁定。
5. 常见问题与排查技巧实录:那些凌晨三点教会我的事
5.1 502 Bad Gateway:Nginx 与 Rails 的“失联”之谜
现象:浏览器访问https://gitlab.example.com显示502 Bad Gateway,但docker ps显示容器Up 10 minutes (healthy)。这是 GitLab 部署中最经典的“假健康”问题。
排查思路:502 意味着 Nginx 作为反向代理,无法连接到上游的 Rails 应用。但gitlab-ctl status显示unicorn和sidekiq都是run状态,说明服务进程活着,只是通信断了。
**