Docker部署GitLab完整初探:从选型、配置到备份恢复与避坑指南
最近因为团队需要一个内部代码托管平台我把Docker部署GitLab这条路从头到尾走了一遍。网上类似文章不少但真正动手时会发现官方文档默认读者是有一定经验的运维人员社区教程又常常把版本差异和端口细节一笔带过。这篇初探记录不会替你敲完所有命令但会把部署链路里那些“没人提醒但迟早会撞上”的环节尽可能点清楚。如果你是第一次接触GitLab或者已经试过一次但卡在页面打不开、克隆地址不对、重启后服务起不来这类问题上这篇内容会比较适合你。我会从选型原因、资源规划、第一次启动、首次配置、备份恢复、瘦身优化和常见坑这几个角度把一趟完整的部署过程拆开讲。不是说教式的步骤罗列而是把我实际操作时怎么想、怎么排查、为什么这样做的逻辑一起写出来。1. 初探之前的选型为什么这次我认准了Docker1.1 裸装版本的一大堆麻烦早几年部署GitLab大家第一反应就是用系统包管理器装GitLab CE。理论上一个系统包就能搞定但实际跑起来之后麻烦会一个个浮出来。GitLab本身依赖很多底层组件PostgreSQL做数据库、Redis做缓存、Nginx做Web入口、Gitaly做仓库存储接入还有一堆Ruby进程。同一个服务器上但凡装过其他Web服务端口冲突和依赖版本冲突就够折腾一阵子。系统包管理器只会告诉你“缺依赖”或者“版本冲突”不会告诉你具体怎么共存。而且GitLab升级的时候动不动就要求先停掉Puma和Sidekiq再执行一系列迁移脚本中间任何一个环节失败轻则页面500重则数据文件版本不一致恢复起来很痛苦。还有一个隐蔽问题裸装GitLab会把自己的文件散落在系统的多个目录里。备份时你以为把主目录拷走就完事实际上配置文件、密钥、证书可能分散在别的地方。一旦机器坏了重新部署一套再导数据能对得上版本算运气好。这些痛点不是不能解决只是对“只是想要一个内部代码仓库”的人来说成本太高了。1.2 Docker化部署对初探者友好的三个特点第一次用Docker部署GitLab后我最大的感受是容器把GitLab的运行环境完整封装了起来宿主机只需要提供资源不需要理解GitLab内部的依赖关系。这个特点带来的最直接好处是升级和回滚变得非常简单。容器镜像本身就是一套“打包好的GitLab”升版本只需要拉新镜像、重建容器、保留数据卷几乎不用碰宿主机上的其他软件。万一新版本有问题换回旧镜像再启动一次数据卷没有动过服务就回去了。这个回滚能力在裸装环境下很难实现。第二个特点是数据卷分离。config、logs、data三个目录各管各的容器可以随时删掉重建但数据留在宿主机上。我刚接触的时候一直担心“docker rm之后数据不就没了”实际上只要volume挂载正确容器随便删数据一个字节都不会少。第三个特点在于隔离性。GitLab自己带的Nginx、PostgreSQL都不会直接暴露到宿主机端口映射是显式的。哪怕宿主机上跑着别的服务只要错开端口两者互不干扰。对初探者来说这相当于多了一层安全垫。2. 打底子资源账、目录规划、它为什么能决定成败2.1 内存怎么算从1GB到8GB的实际感受很多初探者栽的第一个跟头不是命令不对而是内存不够。Docker容器不是“轻量级”的同义词GitLab容器内部跑着一套完整的应用栈该吃的内存一点不少。按照GitLab CE 16.x版本的经验来看资源需求大致可以按下面这张表来估计内存大小能否运行实际体验1GB几乎无法起服务容器启动过程中非常容易OOMreconfigure阶段就会报错2GB可以跑能启动但要关掉部分监控组件大仓库操作明显卡顿4GB官方推荐小团队日常使用流畅合并请求、Code Review体验正常8GB非常宽裕几十人的团队日常使用也足够CI并发高时需要另行评估CPU方面建议至少2核4核是舒服线。GitLab进程很多单核CPU在首次reconfigure和后续仓库操作时都会成为瓶颈。磁盘就更容易忽略GitLab本身会占用10GB以上空间这还不算仓库数据、备份文件和日志膨胀。我建议至少预留50GB否则运行半年后磁盘满了会很被动。不要觉得“Docker只是几个进程占不了多少空间”容器里的镜像层是只读的但GitLab运行时的数据、日志、仓库文件都会写进宿主机目录。我见过有人用20GB的小机器硬跑GitLab结果两个月下来磁盘直接满了连备份文件都放不下。2.2 三个数据卷的划分逻辑config、logs、dataGitLab官方推荐的挂载方式是把三个目录分开映射到宿主机这一点非常关键不要图省事只挂一个根目录。三个卷的职责如下config目录对应容器内的 /etc/gitlab存放gitlab.rb配置文件、SSL证书、初始密钥等logs目录对应容器内的 /var/log/gitlab存放所有服务日志data目录对应容器内的 /var/opt/gitlab存放GitLab数据库、仓库文件、备份产物为什么要这样分开首先备份策略不同。data目录里是真正的业务数据日志目录丢失了不影响功能配置文件则需要单独盯着。其次升级时如果所有文件混在一起镜像层挂载方式一变化容易莫名奇妙的权限问题。分开挂载之后容器重建时的行为是确定的。还有一个容易忽略的细节宿主机上这三个目录要给足权限。GitLab容器内部虽然用root启动但真正的服务进程是以git用户运行的映射出来的目录如果权限太严格容器内进程将无法写入。在Linux上创建这三个目录后不妨先执行一条chown命令把属主调整成容器的git用户ID通常为1000之后再启动容器。这样做虽然不一定每次都需要但能避免很多诡异的“Permission denied”。3. 第一次docker run的完整记录从镜像到页面可用3.1 拉镜像和准备目录时容易被忽略的两个细节第一次操作先把镜像拉下来。我习惯指定大版本而不是无脑用latest初探时可以先用官方镜像熟悉流程但正式使用建议锁定版本docker pull gitlab/gitlab-ce:latest如果希望升级更可控可以换用具体版本标签例如 gitlab/gitlab-ce:16.9.0-ce.0。latest的好处是方便坏处是某一天你docker pull之后容器重建时版本会悄悄变掉而你自己未必记得住了哪个版本。目录准备也很简单mkdir -p /srv/gitlab/config mkdir -p /srv/gitlab/logs mkdir -p /srv/gitlab/data这里有两个细节。第一目录路径不要带中文或空格GitLab内部脚本对路径很敏感。第二如果宿主机已经有别的数据备份习惯尽量把这三个目录放在同一个父目录下后面写备份脚本会轻松很多。3.2 docker run参数逐条解读接下来是核心命令。初探版本我建议把宿主机端口错开不要直接占用80和22因为很多Linux服务器上22已经被SSH服务占用了直接映射会失败。可以先这样跑docker run -d \ --name gitlab \ --hostname gitlab.example.com \ --publish 8080:80 \ --publish 2222:22 \ --restart always \ -v /srv/gitlab/config:/etc/gitlab \ -v /srv/gitlab/logs:/var/log/gitlab \ -v /srv/gitlab/data:/var/opt/gitlab \ --shm-size 256m \ gitlab/gitlab-ce:latest一个一个说。--hostname 参数是给GitLab内部用的它会把这个值作为生成克隆URL的基础。如果你有真实域名就用真实域名如果只是内网IP试试可以先用一个占位主机名后面在配置阶段统一修改。--publish 8080:80 表示把宿主机8080端口映射到容器内80端口访问时要用 http://宿主机IP:8080。--publish 2222:22 表示把宿主机2222端口映射到容器内SSH端口后面配置SSH克隆地址时要用到。--restart always 很关键它保证宿主机重启后容器能自动拉起。如果不加这个参数服务器一重启GitLab就静悄悄停在那里了。--shm-size 256m 是我强烈建议加的容器默认的 /dev/shm 只有64MBGitLab的Puma进程在繁忙时很容易把它写满导致页面500。加大到256MB能避开这个经典问题。三个 -v 参数自然不用多说对应上一章的三个数据卷。-v 的宿主机目录路径要按实际情况修改不要照抄。3.3 启动后如何在日志里判断“真的起来了”执行完docker run后第一件事不是急着开浏览器而是看日志。GitLab容器启动过程比较长第一次往往要等2到5分钟期间它要做初始化数据库、生成密钥、执行reconfigure等一堆事情。docker logs -f gitlab你要在日志里看到类似“gitlab Reconfigured!”或者“GitLab is up and running”之类的标志才算真正启动完成。不要看到几行输出就以为成功了GitLab的启动是分阶段的前期甚至可能没有任何输出那也正常。另外可以用这个命令确认关键服务状态docker exec -it gitlab gitlab-ctl status正常情况下会看到run: alertmanager、run: gitaly、run: nginx、run: postgresql、run: puma、run: sidekiq等一堆服务。如果某些服务显示down说明还在启动或出了问题不要急着重启容器先等再观察。我在第一次启动时就是因为太心急反复docker restart反而把初始化过程打断了最后只能清空data卷重新来过。启动完成后GitLab会在容器内生成一个临时root密码文件。你可以通过这个命令查看docker exec -it gitlab cat /etc/gitlab/initial_root_password这个文件会在24小时后自动删除所以首次登录后一定要尽快修改密码。4. 首次配置GitLab外部访问、root密码和注册开关4.1 EXTERNAL_URL背后的坑只改端口不改主机名会很难受容器跑起来后浏览器访问 http://宿主机IP:8080 通常能看到GitLab登录页。但页面里生成的仓库克隆地址很可能不是你访问用的地址而是docker run时指定的--hostname值。比如你访问的是192.0.2.1:8080但页面显示的是gitgitlab.example.com:group/repo.git这就是外部URL没配置好。GitLab判断克隆地址的依据是external_url它默认由--hostname推导。想让GitLab按照真实方式工作最好在配置里显式指定external_url。如果只是内网IP测试可以这样设置external_url http://192.0.2.1:8080 gitlab_rails[time_zone] Asia/Shanghai文件位置在挂载出来的config目录下的gitlab.rb。直接在宿主机上编辑就行不用进容器vim /srv/gitlab/config/gitlab.rb这里要特别注意external_url里必须带端口。因为我的web访问端口是8080如果只写http://192.0.2.1GitLab生成的重定向和克隆地址都会少了端口浏览器访问时同样会出现端口不对。改了external_url之后旧的克隆URL会失效成员需要重新拉取一次仓库地址。所以初探阶段最好一开始就确定好访问方式别上线了再改。4.2 改完gitlab.rb之后的reconfigure流程修改gitlab.rb之后需要让GitLab内部重新生成配置并加载这个动作叫reconfigure。在容器里执行docker exec -it gitlab gitlab-ctl reconfigure这个过程会重新生成Nginx配置、GitLab Shell配置并重启相关服务。耗时一般几十秒到几分钟。reconfigure过程中不要强行中断否则可能留下半套新配置和半套旧配置混用的状态。我建议改配置之前先备份一份gitlab.rb出现问题时能迅速还原cp /srv/gitlab/config/gitlab.rb /srv/gitlab/config/gitlab.rb.bak如果reconfigure中途报错把报错信息复制出来搜一下很大概率是配置项写错了或者端口冲突。GitLab的错误信息虽然长但通常会在最后几行给出关键线索。4.3 小团队该第一时间做掉的三个安全设置登录方式上GitLab首次登录可以用initial_root_password文件里的临时密码。登录后第一件事就是去右上角头像处修改root密码这个不用多说了。然后建议立刻关闭公开注册。GitLab默认情况下是允许任何人注册的如果不关你的内网GitLab很快会被各种机器人注册垃圾账号。关闭路径是Admin Area → Settings → General → Sign-up restrictions取消勾选“Sign-up enabled”。不同版本菜单名称略有差异但基本都在这条路径附近。第三个是代码可见性设置。默认情况下新建项目可以是Public这在内网环境里容易造成代码意外公开。建议在Admin Area → Settings → General → Visibility and access controls里把“Restricted visibility levels”限制为Internal或Private只允许内部或私有项目。这样即使有成员误建项目也不会直接裸露给所有人。5. 日常运维三件事备份、恢复、升级5.1 备份到底备份了什么GitLab自带的备份命令是docker exec -t gitlab gitlab-backup create这个命令备份的是GitLab的数据库、仓库文件、上传附件等核心数据备份文件会生成在容器内的 /var/opt/gitlab/backups 目录下也就是宿主机上的 /srv/gitlab/data/backups 目录。但要注意gitlab-backup create不会自动备份/etc/gitlab下的配置文件。gitlab.rb、密钥、证书这些文件不在备份产物里。所以一套完整的备份策略是除了执行gitlab-backup create还要额外复制一份config目录。两个部分都拿到才是完整可恢复的状态。备份文件可以设置保留时间。在gitlab.rb里加一行backup_keep_time 604800单位是秒604800刚好是7天。设置后GitLab会在每次备份时自动清理超过7天的旧备份避免备份目录无限膨胀。初探者容易忘记这个配置等发现磁盘满了才回头找原因。5.2 恢复演练换一台机器也能拉起来的验证过程备份如果不做恢复演练基本等于没备份。我自己第一次做恢复时就遇到过备份文件权限不对导致restore失败的情况。恢复前先停掉可能占用数据库连接的服务docker exec -it gitlab gitlab-ctl stop puma docker exec -it gitlab gitlab-ctl stop sidekiq然后把备份文件放到 /srv/gitlab/data/backups 目录下。注意备份文件的所有者GitLab的restore命令会要求备份文件属于git用户否则会提示权限错误。如果发现权限不对在容器内执行docker exec -it gitlab chown git:git /var/opt/gitlab/backups/备份文件名执行恢复命令时BACKUP参数只需要写备份文件名中的那一串时间戳和版本号部分。例如文件是 1684567890_2024_05_20_16.9.0_gitlab_backup.tar那么命令是docker exec -it gitlab gitlab-backup restore BACKUP1684567890_2024_05_20_16.9.0恢复过程会覆盖现有数据库和仓库数据所以一定要确认这是你想要的操作。恢复完成后重启容器docker restart gitlab我第一次做恢复演练时特意把这套流程在另一台机器上跑了一遍新机器装Docker挂载空目录先启动一个同样版本的GitLab容器再把备份文件和config目录放进去最后执行restore。整个过程比想象中顺利最大的价值是确认了“备份文件config目录”两件套足够完成迁移。5.3 版本升级的稳妥顺序Docker化部署让GitLab升级变成了一件很舒服的事比裸装版本要安全很多。我的经验是分四步走第一步升级前先备份并且确认备份文件完整。第二步拉取目标版本的镜像不要跨大版本跳。例如从16.8升到16.9是安全的从15.x直接跳到16.x通常也允许但从旧版本跨多个大版本跳会有风险建议逐个大版本走一遍。第三步停止并删除旧容器docker stop gitlab docker rm gitlab第四步用之前相同的docker run命令重新启动只是把镜像换成新版本。数据卷没有动所有配置和数据都还在。启动后等待reconfigure完成再用gitlab-ctl status确认服务正常。这个过程中最容易犯的错误是升级前没有备份或者备份文件保留时间太短导致旧备份已被清理。另外每次升级后都去后台看一眼版本号并且做一次页面访问和仓库克隆验证再决定是否继续下一步。别一次把所有版本都升完稳一点比快一点重要。6. 资源不够时的瘦身思路关哪些服务、怎么关6.1 哪些组件可以关哪些不能关GitLab容器内默认跑着一整套服务但并不是每一个都适合小团队场景。如果你只有2GB内存可以关闭部分监控组件来缓解压力。在gitlab.rb里加上以下几行然后重新reconfigureprometheus_monitoring[enable] false grafana[enable] false alertmanager[enable] false gitlab_pages[enable] false组件负责的事情小团队能否关Prometheus采集GitLab指标可以先关对功能无影响Grafana指标面板展示可以先关Alertmanager告警通知可以先关GitLab Pages托管静态网站不用就关PostgreSQL核心数据库绝对不能关Redis缓存和队列绝对不能关Gitaly仓库存储访问绝对不能关Puma SidekiqWeb和后台任务绝对不能关关掉Prometheus、Grafana和Alertmanager能省下几百MB内存。Prometheus本身还按时间序列写数据长期运行会占用不少磁盘空间对于不需要精细化监控的团队关闭是合理的。GitLab Pages是给每个项目托管静态站点的功能内部团队基本用不上关掉无妨。还有一组容易踩坑的组件Nginx。很多教程会建议把容器内的Nginx关掉改用宿主机Nginx反代。我不建议初探者这么做因为GitLab内部很多链接生成逻辑都依赖Nginx和external_url配合一旦关掉内置Nginx出问题时排查范围会变大很多。等你对GitLab足够熟悉了再考虑搞外部反代不迟。6.2 内存不够时优先加SWAP而不是一心限制容器内存有些初探者一看内存紧张立刻给容器加 --memory 参数希望通过限制容器内存来防止OOM。这种做法在GitLab场景下很容易适得其反。GitLab内部各个进程加起来需要的内存是实打实的强行限制会导致Puma进程被杀页面大面积报错。我建议先考虑加大SWAP。对于2GB内存的机器可以分配一个4GB的swap文件fallocate -l 4G /swapfile chmod 600 /swapfile mkswap /swapfile swapon /swapfile为了让swap在重启后依然生效还要在 /etc/fstab 里加上一行挂载记录。这个操作对Docker容器同样有效因为SWAP是宿主机层面的容器内进程内存压力过大时会被换出到SWAP。加完SWAP后用docker stats观察容器内存占用docker stats gitlab如果确认内存稳定在某个区间再决定是否需要进一步关闭组件或者增加物理内存。加SWAP只是缓冲长期运行还是建议内存至少4GB。7. 初探期最容易踩的三个坑7.1 因为shm太小导致的Rails崩溃第一次跑起来后正常使用了半天突然有人访问时页面变成500或者仓库页面转圈很久后报错。打开容器日志会看到类似“No space left on device”但磁盘明明没满这种时候大概率是 /dev/shm 满了。原因是容器默认 /dev/shm 只有64MB而GitLab的Puma在渲染大量代码时会把临时文件写到这个目录里。解决办法就是docker run时加 --shm-size 256m前面已经提过。如果你已经启动了容器那只能停掉容器、用带 --shm-size 的命令重新创建容器然后等待重新启动。这个坑很经典遇到一次就能记住。7.2 端口改了但SSH克隆地址不对用8080映射80之后Web界面可以正常访问了但SSH克隆地址仍然指向22端口。因为我在docker run时把宿主机的2222映射到了容器内22所以需要让GitLab知道SSH端口变成了2222。路径是Admin Area → Settings → Network → GitLab Shell把SSH端口设置成2222。保存后页面生成的SSH克隆地址会带上端口像这样ssh://git192.0.2.1:2222/group/repo.git。如果不去改这个设置成员点击SSH克隆时拿到的地址缺了端口克隆直接超时。还有一种手动方式是给成员提供完整URL。但管理员最好还是把系统层面的SSH端口改对否则每个项目都要手动解释一遍管理员会疯掉。7.3 restart always不是万能的要给它预留恢复时间--restart always 保证了容器在宿主机重启后能自动拉起但它能做的只是“启动容器”不包括“等GitLab内部服务全部就绪”。如果容器起来之后你立刻去访问页面大概率会看到502或者连接拒绝这是正常的不是故障。我有一个习惯宿主机重启后先等两三分钟再连GitLab页面。不要一看到502就docker restart gitlab重启会打断内部的初始化任务反而让恢复更慢。更准确的做法是观察日志直到出现“GitLab is up and running”再开始用。如果希望自动化一点可以给容器配置健康检查让Docker在健康状态通过后再开始提供服务。初期手动观察日志就够了等业务稳定后再考虑更深度的健康检查方案。以上几个坑是我这次初探Docker部署GitLab时印象最深的几个点。如果你准备动手我建议先找一台闲置机器把从拉镜像、启动、备份到恢复的整条链路完整跑一遍。尤其是恢复演练做完一次之后你后面升级和迁移的心态会踏实很多。