代码托管是所有自动化流程的地基,不管团队用 Jenkins、GitLab CI 还是其他流水线,仓库稳不稳直接决定后面每个环节的体验。GitLab 在这类方案里算是最均衡的选择之一:既能托管代码、做代码评审,又自带 CI/CD,还支持私有化部署。但一提到私有化部署,很多人头皮就麻——Ruby 生态那套安装流程,依赖多、内存吃紧、配置项杂,光折腾环境就能耗掉半天。后来我干脆改用 Docker 部署 GitLab,环境差异被直接抹平。一台 8G 内存的云服务器,从拉镜像到进网页建仓库,前后十几分钟搞定。这篇文章我就把整个过程完整拆一遍,从方案选型、部署参数到日常维护、CI/CD 联动和常见排错,所有配置都是可以直接抄作业的版本,尤其适合正在从零搭研发基础设施的小团队。
1. 先从方案选型说起:为什么用 Docker 装 GitLab
1.1 传统安装与容器化部署的真实对比
GitLab 官方其实提供了多种安装方式,最常见的是在 Debian/Ubuntu 上用apt直接装 Omnibus 包。这条路本身没问题,但有几个很现实的情况:一是 Omnibus 包含了一整套 Ruby、PostgreSQL、Redis、Nginx 等组件,升级和卸载都会牵动系统全局;二是如果你同时跑着 Jenkins、Nexus、数据库等中间件,包管理器层面的依赖冲突迟早会来;三是团队里如果有人环境是 CentOS,有人是 Ubuntu,每个人的安装步骤都得单独写文档。
Docker 化之后,这些麻烦基本被抹平。GitLab 官方镜像把整套运行环境封装好了,你在哪台机器上跑,行为都是一致的。升级就是换一个镜像 tag,回滚也是改一行配置的事。我经历过几次 Omnibus 升级时数据库迁移卡死的情况,切到 Docker 之后这种焦虑少了一大半。
还有一个很多人没意识到的好处:容器化部署让“环境隔离”这件事变得很彻底。代码仓库的权限、网络策略、资源限制,都可以通过 Docker 的网络和资源参数单独管控。比如给容器限制内存,避免 GitLab 的 Ruby 进程把整台机器吃满,这在多服务共存的服务器上非常实用。
1.2 资源规划:8G 内存够不够,端口怎么分
GitLab 官方推荐最低 4G 内存,但这只是“能跑”的门槛。我实测下来,单容器使用 + 一个小团队十几个人日常操作,4G 内存下 GitLab 会在合并请求、CI 任务并发时明显变慢,有时还会出现 502。8G 内存是体感比较顺滑的配置,如果再开 Runner 执行构建,建议机器整体给到 16G。
磁盘方面,GitLab 对 IO 要求比想象中高。PostgreSQL 和 Git 仓库的读写都是随机 IO,机械硬盘基本别想,SSD 是底线,云服务器选高效云盘或 ESSD 会稳很多。空间上除了仓库本身,还要留出镜像、备份和日志的空间,我一般建议至少给 50G。
端口规划也值得提前想清楚。GitLab 默认会用 80 和 443,但实际部署的服务器上,80 端口很可能已经被 Nginx、Jenkins 或其他 Web 服务占了。我在第一次部署时就吃过这个亏,默认端口冲突导致 Nginx 直接起不来。所以后面我固定用 8088 映射 GitLab 的 8080 Web 端口,用 2224 映射 SSH 端口,避开常见服务的默认端口段。这样后面再接 Runner、配 Webhook 都不用担心端口打架。
2. 部署前的关键准备
2.1 Docker 环境本身怎么装,Windows/macOS 有哪些坑
Linux 服务器上安装 Docker 就一句话的事:用官方脚本装完,把当前用户加进 docker 组,再重启一下相关服务。Windows 和 macOS 上一般用 Docker Desktop,这个工具有个著名的坑——启动时报virtualization support wasn't detected或virtualization support wasn't detected这类的错误,本质上是因为本机虚拟化没开,或者 WSL2 内核没更新。
我之前帮同事处理过一台 Windows 机器,折腾半天发现就是 BIOS 里的 Intel VT-x 没启用。另外 Windows 家庭版对 Hyper-V 的支持不完整,需要先升级到 WSL2,再安装 Linux 内核更新包,Docker Desktop 才能正常起来。macOS 上如果碰到虚拟化相关报错,先检查是不是在虚拟机里跑的 macOS,没有嵌套虚拟化的话 Docker Desktop 基本是没法用的。
2.2 目录挂载与数据持久化,别让代码一夜蒸发
Docker 容器本身是无状态的,只要容器被删,里面所有数据就没了。所以 GitLab 的持久化数据必须通过挂载卷映射到宿主机目录上。这恐怕是整个部署过程中最重要的一件事,没有之一。
官方推荐挂载三个目录:
/etc/gitlab:存放 GitLab 的配置文件/var/log/gitlab:日志文件/var/opt/gitlab:代码仓库、数据库、备份等核心数据
我的习惯是把它们统一放到宿主机的/srv/gitlab下面。这样做的好处是:备份只需要打包/srv/gitlab目录;排查问题时日志全在一个地方;升级容器时只要挂载目录不变,数据就完完整整还在。
注意:
/var/opt/gitlab是 GitLab 的核心数据目录,代码仓库、数据库都在这下面。这个目录一旦损坏或丢失,几乎没有恢复的可能。挂载路径一经确定就不要随意改动,改挂载路径等于换了一个新实例。
3. 完整部署流程:从镜像到首次登录
3.1 写一份能直接用的 docker-compose.yml
部署方式上,直接docker run一条长命令当然可以,但参数一多就容易漏,后面想改配置也不方便。我用的是docker-compose,把容器定义写进一个 YAML 文件,升级、启停、查看日志都只要一行命令。
下面是我现在还在用的配置文件,直接保存为docker-compose.yml就能启动:
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:8088' gitlab_rails['gitlab_shell_ssh_port'] = 2224 gitlab_rails['time_zone'] = 'Asia/Shanghai' user['home'] = '/var/opt/gitlab' ports: - '8088:8080' - '2224:22' volumes: - /srv/gitlab/config:/etc/gitlab - /srv/gitlab/logs:/var/log/gitlab - /srv/gitlab/data:/var/opt/gitlab shm_size: '256m'解释几个关键参数:
hostname和external_url:决定了 GitLab 生成的仓库 clone 地址显示什么域名,这个下面单独说,是最容易被忽略的坑。gitlab_shell_ssh_port = 2224:因为容器内 SSH 用的是 22 端口,而宿主机上 22 端口通常已经被系统 SSH 占了,所以映射到外部 2224。这个参数不配的话,clone 时生成的 SSH 地址端口就是错的。restart: always:机器重启后容器能自动拉起,省心。shm_size:设置共享内存大小,GitLab 内部用到了,给太小容易在特定操作时报错。user['home']:固定 Git 用户家目录,避免后续升级时 home 路径漂移造成问题。
端口映射这里有个细节:8088:8080里的 8080 是容器内 GitLab 默认的 web 端口,8088是宿主机对外的端口。如果你服务器上 80 端口是空闲的,也可以写80:8080,访问不加端口更顺手。但一旦用了非 80 端口,external_url里就必须带上端口号,否则 clone 出来的地址会指向错误端口。
3.2 启动、初始化与 root 密码获取
配置写好后,在docker-compose.yml所在目录执行:
docker-compose up -d第一次启动会拉镜像,然后初始化数据库和各个组件,这个过程比较长,通常要 3 到 5 分钟,配置差的机器可能更久。不要着急去访问页面,先看日志:
docker logs -f gitlab看到Starting GitLab相关的日志逐渐安静下来,或者日志里出现gitlab Reconfigured!字样,基本就绪了。判断是否真正可用的方法,通常是直接等一下再敲命令确认:
docker exec gitlab grep 'Password:' /etc/gitlab/initial_root_passwordGitLab 首次启动时会生成一个临时 root 密码,路径在/etc/gitlab/initial_root_password。拿到密码后,浏览器访问http://服务器IP:8088,用用户名root登录,进去第一件事就是把初始密码换掉。这个临时密码文件只在 24 小时内有效,如果超时没记下来,后面要用别的方式重置密码。
3.3 clone 地址总显示容器 ID,怎么改
这是 Docker 部署 GitLab 被问得最多的问题:新建项目后,clone 地址显示的 host 是容器 ID 而不是域名。原因很简单,容器 ID 就是容器的主机名,如果external_url没设置或设置不对,GitLab 就会拿容器主机名拼地址。
解决办法是修改external_url。我习惯在docker-compose.yml的环境变量里直接写,然后重新创建容器:
docker-compose down docker-compose up -d如果你已经部署完才改,也可以进容器修改/etc/gitlab/gitlab.rb:
docker exec -it gitlab vi /etc/gitlab/gitlab.rb找到external_url这一行,改成你的真实域名或 IP,然后执行:
docker exec gitlab gitlab-ctl reconfigure注意reconfigure会重新生成配置,耗时也不短,但这是让配置生效的必经步骤。执行完之后,再回项目页面看 clone 地址,host 就是刚才设置的域名了。
关键点:域名不要写
localhost或127.0.0.1。如果团队多人协作,别人 clone 时解析不了这个地址。写内网域名或公网 IP 都行,前提是大家都能访问到。
4. 日常使用中的高频配置
4.1 SSH 密钥配置,告别频繁输密码
HTTP 克隆每次都要输入账号密码,时间长了对谁都是折磨。配置 SSH 密钥一次搞定。在本地机器执行:
ssh-keygen -t ed25519 -C "your_email@example.com"一路回车生成密钥,然后查看公钥:
cat ~/.ssh/id_ed25519.pub复制公钥内容,在 GitLab 页面右上角头像 → Preferences → SSH Keys 里粘贴保存。验证是否配置成功,执行:
ssh -T git@gitlab.example.com -p 2224如果提示Welcome to GitLab之类的信息,就说明通了。注意这里端口必须跟部署时映射的 SSH 端口一致,否则连接会被拒。我之前在配置文件里没写gitlab_shell_ssh_port,结果本地 ssh config 写一堆别名才绕过去,后面补上这个参数,一劳永逸。
有同学问“本地 Git 怎么同时配置 GitHub 和公司 GitLab”,这个其实很简单。GitLab 的 SSH 服务默认占 22 端口,如果公司服务器映射到 2224,那就生成密钥后,在~/.ssh/config里加:
Host gitlab.example.com HostName gitlab.example.com Port 2224 User git IdentityFile ~/.ssh/id_ed25519GitHub 用默认配置不动,两者互不干扰。
4.2 导入项目、删除仓库、分支合并
从零开始建项目很简单,页面上点 New Project,然后把本地代码推上去:
git remote add origin http://gitlab.example.com:8088/group/project.git git branch -M main git push -u origin main如果你是从 GitHub 或其他 GitLab 迁移项目,导入功能更加省事。顶部菜单 New Project → Import Project,选对源平台,填上对应 Token 就能把仓库、分支、标签都拉过来。这里有个小注意点:导入过来的项目,Webhook 和 CI/CD 变量这些配置不会被迁移,需要重建。
删除仓库的操作也要说一下,很多人找不到入口。进入项目 → Settings → General → 拉到最底部,有个 Advanced 区域,展开后就能看到 Remove project。删除是不可逆操作,仓库、所有分支、合并请求记录全部清空,建议删除前先确认有没有备份,或者把仓库先导出。
分支管理方面,我建议给 main/master 分支开启保护。在 Settings → Repository → Protected Branches 里配置,这样普通成员无法直接 push 受保护分支,必须走合并请求。配合合并请求的审批规则,代码质量就有了最基础的一道保障。团队里的合并操作一般建议用页面上的 Merge Request 而不是自己拉代码手动 merge,至少留个记录,出问题好回溯。
5. 把仓库用起来:CI/CD 流水线联动
5.1 注册 Runner 的完整过程
GitLab CI/CD 要跑起来,必须有一个 Runner 来执行任务。Runner 可以装在和 GitLab 同一台机器,也可以装在独立的服务器上。我这里以最常见的 Docker executor 为例,把 Runner 也用一个容器跑起来。
先到 GitLab 页面拿到注册令牌:Settings → CI/CD → Runners → 展开 Runner 设置,那里有registration token。然后执行:
docker run -d --name gitlab-runner --restart always \ -v /srv/gitlab-runner/config:/etc/gitlab-runner \ -v /var/run/docker.sock:/var/run/docker.sock \ gitlab/gitlab-runner:latest注册的时候执行:
docker exec -it gitlab-runner gitlab-runner register按提示依次输入 GitLab 地址、注册令牌、Runner 描述、执行器类型(填docker),再填默认镜像(比如docker:24.0)。注册完成后,回到 GitLab 页面的 Runner 列表,能看到该 Runner 已经显示在线。
这里要重点说一个我的实际操作心得:Runner 容器挂了 Docker socket,是为了让它能直接调用宿主机的 Docker 来构建镜像。但这样做有一个副作用——Runner 能操作宿主机上的所有 Docker 容器,权限非常大。如果是小团队内部用,问题不大;如果环境敏感,建议用 Kubernetes executor 或单独建一个 Runner 专用虚拟机,避免权限放大。
5.2 一份能跑通的 .gitlab-ci.yml
仓库根目录下新建.gitlab-ci.yml,这是流水线的核心配置。我给一个最简但完整的例子:
stages: - build - deploy before_script: - echo "开始执行流水线" build-job: stage: build script: - echo "编译代码" - docker build -t registry.example.com/demo:$CI_COMMIT_SHORT_SHA . - docker push registry.example.com/demo:$CI_COMMIT_SHORT_SHA only: - main deploy-job: stage: deploy script: - echo "部署到服务器" - docker pull registry.example.com/demo:$CI_COMMIT_SHORT_SHA - docker stop demo-app || true - docker rm demo-app || true - docker run -d --name demo-app -p 8081:80 registry.example.com/demo:$CI_COMMIT_SHORT_SHA only: - main needs: - build-job几个内置变量说明一下:CI_COMMIT_SHORT_SHA是当前提交的短 SHA,用这个做镜像 tag 能保证每个提交的镜像都是唯一的;CI_PROJECT_PATH是项目路径;CI_REGISTRY是内置镜像仓库地址。实际使用中,把镜像版号统一按$CI_COMMIT_SHORT_SHA来打,回滚时只需要重新跑对应 tag 的镜像,不用猜版本对应关系。
5.3 Docker 镜像构建与部署实战
Runner 在 Docker executor 模式下,每次任务都会基于指定的镜像起一个全新容器。这意味着任务里要用 Docker 命令,需要挂载 Docker socket(就是上面注册 Runner 时挂的/var/run/docker.sock),同时任务容器会提示没有访问 docker 守护进程的权限,这就是很多人遇到的“docker: command not found”或 socket 权限问题。
解决办法有两个方向。一个方向是把 Runner 容器设置成 privileged 模式,但这个权限放大太明显,我不是很推荐。另一个更通用的做法是,在任务里用 docker-socket 代理,也就是在 Runner 配置的[[runners.docker]]部分加:
privileged = true这里privileged主要影响的是 DinD(Docker in Docker)方案。GitLab 官方文档推荐的 DinD 方式,任务容器里再起一个 Docker daemon,构建镜像完全在任务内部完成,不会污染宿主机。代价是每个任务都要等 daemon 启动,慢几秒,但隔离性最好。
实际经验是:如果团队规模小、流水线不复杂,直接用挂 socket 的方式最省事;如果任务多了、并发高了,再考虑 DinD。两条路都踩过,DinD 初期遇到“连接不到 daemon”的概率非常高,多半是privileged没开,或者 daemon 启动参数要加--tls=false。刚开始跑 CI,先选简单的方案,把流程跑通再去优化。
6. 安全与稳定性:不可跳过的一节
6.1 高危漏洞修复和版本升级
GitLab 历史上出过不少安全漏洞,尤其是未授权访问、命令执行这类高危问题,影响面很大。应对思路其实很简单:保持版本更新。很多人装完 GitLab 就再也没管过,直到出了问题才想起来升级,这是比较危险的做法。
我建议至少每月看一次 GitLab 官方发布的版本更新和安全公告。如果是 Docker 部署,升级就是换镜像 tag 然后重建容器的事:
docker-compose pull gitlab docker-compose up -d但升级前一定一定要做两件事:一是备份数据,二是看官方升级路径。GitLab 官方对跨大版本升级有严格限制,比如从 14 直接升到 16 可能不被允许,必须逐版本升级。依赖包虽然被 Docker 封装了,但底层数据库迁移一样存在,跳版本升级导致数据库迁移失败的例子我见过不止一次。稳妥的做法是先查官方升级文档,确认当前版本到目标版本是否在允许路径内,必要时先升到中间版本。
6.2 备份与恢复
Docker 部署的备份有两条路。一条是用 GitLab 自带的备份命令:
docker exec gitlab gitlab-backup create备份文件会生成在/var/opt/gitlab/backups目录下,也就是挂载在宿主机的/srv/gitlab/data/backups里。恢复时用:
docker exec gitlab gitlab-backup restore BACKUP=时间戳文件名另一条路是直接备份整个/srv/gitlab目录。这个方法更粗暴,但恢复时要求版本一致,因为数据库文件和代码仓库文件都跟特定版本绑定。我自己的习惯是双保险:GitLab 自带备份每天跑一次,同步到对象存储;整个目录的快照每周做一次。公司就吃过备份不全的亏,项目删库之后才发现备份脚本里漏了新加的仓库路径,从那以后,每次改配置我都会顺手验证一遍备份能否恢复,这个习惯强烈建议保留。
7. 常见问题速查与避坑实录
这里把我在实际部署和使用中遇到的高频问题整理成一张速查表,遇到类似问题可以直接对照排查。
| 现象 | 可能原因 | 处理方法 |
|---|---|---|
| 部署后访问网页返回 502 | GitLab 内部组件还没初始化完成,或内存不足 | 执行docker logs -f gitlab观察日志,等待初始化结束;检查内存是否低于 4G |
| Docker Desktop 启动报 virtualization support 相关错误 | BIOS 中未开启虚拟化,或 WSL2 未正确启用 | 重启进 BIOS 开启 VT-x/AMD-V;更新 WSL2内核;关闭 Hyper-V 冲突项 |
| clone 地址显示容器 ID 而非域名 | external_url未配置或配置不正确 | 修改external_url,执行gitlab-ctl reconfigure |
| SSH clone 提示拒绝连接 | 宿主机 SSH 端口映射与gitlab_shell_ssh_port不一致 | 检查docker-compose.yml端口映射,确保两端端口一致 |
| HTTP clone 输入密码后不能 clone | 账号开启了 2FA,需要使用个人访问令牌代替密码 | 在用户设置中创建 Personal Access Token,clone 密码填 token |
| Jenkins 连接 GitLab 报 login failed,check api token or gitlab version | API Token 无效,或 GitLab 版本与插件不兼容 | 重新生成 Access Token,更新 Jenkins GitLab 插件;确认 token 权限包含 API 范围 |
| Runner 显示离线 | Runner 容器的注册令牌过期,或网络不通 | 重新注册 Runner,确认 GitLab 地址是否可访问 |
| Docker 构建任务提示 permission denied | Runner 的 privileged 未开启,或 socket 权限不足 | 在 Runner 配置中设置privileged = true,确认 socket 挂载正确 |
| 磁盘占用增长很快 | GitLab 日志、镜像、备份文件堆积 | 定期清理容器日志和旧镜像,备份文件及时迁移到异地存储 |
| 推送代码后 CI 不触发 | 分支保护设置,或.gitlab-ci.yml文件不在默认分支 | 确认流水线配置已推送到 main 分支,检查分支保护规则是否阻止了 push 事件 |
| 迁移项目后 webhook 丢失 | GitLab 导入功能不迁移集成配置 | 重建 Webhook 和 CI/CD 变量,核对对外服务的回调地址 |
最后一个没人提但很值得说的坑:如果你在公司内网部署 GitLab,而本地同时也在用多个 Git 平台,记得把 GitLab 的 clone 地址、SSH 端口、用户名写进.git/config或~/.ssh/config,不然每次切换平台都会撞一次配置冲突。我就是把这些配置模板放到团队 wiki 里,新同学入职照着抄一遍,基本不用我再远程指导。
这个内容后续还可以这样扩展:等 GitLab 跑稳之后,可以接一套容器镜像仓库进去,把构建产物统一管理起来;也可以在 Jenkins 里把 GitLab 的 MR、分支、Tag 事件串成更复杂的自动化流程。我个人的体会是,代码仓库这种基础设施,一开始别追求一步到位,先把部署、备份、权限这三件事做对,后面再慢慢加 CI/CD、加安全扫描,整个研发体系才不容易被某个单点拖垮。