Docker部署GitLab全攻略:从环境准备到备份升级与CI/CD联动
1. 为什么我在生产环境选了 Docker 部署 GitLab1.1 传统安装与容器化部署的取舍先交代一下背景。我在过去维护过两套 GitLab一套是直接在 CentOS 上通过 rpm 包装的另一套就是后来全部切换到的 Docker 部署方案。如果你问我哪套更省心我会毫不犹豫告诉你Docker 那套省事太多了。传统方式装 GitLab 社区版最头疼的问题就是依赖。GitLab 官方虽然提供了 omnibus 包把 Redis、PostgreSQL、Nginx 这些组件都打包进去了但你要是真在裸机上装过一次就知道它牵扯的 systemd 服务、文件权限、防火墙端口到底有多绕。尤其当你同时维护多台服务器需要统一版本、迁移环境的时候传统安装简直就是噩梦。换成 Docker 之后整个 GitLab 变成一个可以被声明式管理的服务。镜像一旦跑起来所有中间件都封装在容器内部主机上只留下数据目录、配置目录和日志目录三个挂载点。迁移的时候只要把这三个目录打包带走另一台机器上跑同一个 compose 文件就能无缝还原根本不用担心底层系统版本差异。另外还有一个很现实的点如果你以后要接 CI/CDGitLab Runner 同样可以用容器方式跑整个工具链的交付形态完全统一不再需要为不同组件单独维护安装文档。对于一个人要管好几台服务器的场景来说这个优势非常明显。1.2 硬件与系统环境的最低门槛很多人会问用 Docker 跑 GitLab 到底需要什么配置我的经验是2 核 4G 内存勉强能跑4 核 8G 才算舒服8 核 16G 可以支撑二三十人的小团队日常使用。主要瓶颈在内存GitLab 全家桶启动后常驻内存大约 2G 到 3G如果你还要跑 Runner 执行构建任务内存就得往上加。系统方面没有太多挑剔主流 Linux 发行版都行Ubuntu 20.04、22.04 和 CentOS 7.9、 Rocky Linux 我都实际跑过。Docker 版本尽量用 20.10 以上Docker Compose 用 v2 版本。Windows 做开发机跑 Docker Desktop 玩玩可以生产环境不推荐原因很简单文件挂载性能和权限模型在 Windows 上会给你带来一堆莫名其妙的麻烦。提示如果你只有一台 1 核 2G 的云服务器建议别硬上 GitLab 全家桶可以退一步用 Gitea 或者 Gogs它们对资源的要求低得多。硬上 GitLab 的结果通常是容器频繁 OOM服务隔三差五就挂。2. 部署前的目录规划与环境准备2.1 宿主机规划存储路径与端口分配部署之前先把目录结构想清楚。GitLab 容器里有三个核心目录需要挂载出来/etc/gitlab存放 gitlab.rb 配置文件和 SSL 证书/var/opt/gitlab存放 Git 仓库数据、数据库、附件等核心数据/var/log/gitlab存放日志我习惯把它们放在宿主机的一个统一目录下比如/data/gitlab然后按config、data、logs三个子目录区分。这里有个小建议数据目录所在的磁盘最好用独立的数据盘不要和系统盘混在一起不然系统盘写满之后整个服务器都会出问题。端口分配方面GitLab 默认用 80 和 443但实际部署时非常不建议直接用宿主机 80 端口因为你可能还有其他 Web 服务。我一般会把宿主机端口映射为自定义端口比如 8080 映射到容器的 808443 映射到容器的 443再通过 Nginx 做反向代理统一对外。SSH 端口我习惯用 2222 映射容器的 22这样宿主机自己的 SSH 保持默认的 22 不受影响。2.2 Docker 环境检查与镜像选择开始之前先检查一下 Docker 环境# 确认 Docker 版本 docker --version # 确认 Docker Compose 版本 docker compose version # 确认磁盘空间GitLab 运行至少需要 10G 可用空间 df -h镜像选择上我推荐直接使用官方镜像gitlab/gitlab-ce:latest但生产环境更建议锁定到一个具体的版本号不要用latest。因为 GitLab 版本升级有时候涉及数据库迁移如果跨大版本升级处理起来会比较麻烦。锁版本之后你想升级的时候可以明确地控制节奏而不是某天重启容器发现镜像自动变成了新版本结果数据迁移出了问题。另外提醒一点如果你是第一次拉取镜像官方镜像大约 1.6G在网速一般的情况下可能要等一段时间。国内网络环境建议配置 Docker Registry 镜像加速器这个你可以在 Docker 的daemon.json里配置具体镜像源地址根据你的实际情况选择。3. 完整部署过程与核心编排方案3.1 使用 docker-compose 定义服务我个人强烈建议用 Docker Compose 而不是直接docker run原因有两个一是配置可维护不会因为时间长忘记当初的启动参数二是可以纳入版本管理后续加 Runner、加其他服务时统一管理。下面是我实际使用的docker-compose.yml你可以直接拿来改version: 3.8 services: gitlab: image: gitlab/gitlab-ce:16.10.2-ce.0 container_name: gitlab restart: always hostname: gitlab.example.com environment: GITLAB_OMNIBUS_CONFIG: | external_url http://gitlab.example.com:8080 gitlab_rails[gitlab_shell_ssh_port] 2222 gitlab_rails[time_zone] Asia/Shanghai unicorn[worker_processes] 2 puma[worker_processes] 2 postgresql[shared_buffers] 256MB sidekiq[max_concurrency] 5 gitlab_rails[initial_root_password] YourStrongPassword123! ports: - 8080:80 - 8443:443 - 2222:22 volumes: - /data/gitlab/config:/etc/gitlab - /data/gitlab/data:/var/opt/gitlab - /data/gitlab/logs:/var/log/gitlab shm_size: 256m注意几个关键参数hostname和external_url这两个必须保持一致不然克隆地址会显示错误。如果你暂时没有域名可以直接用http://服务器IP:8080后面解析了域名再改配置。GITLAB_OMNIBUS_CONFIG这个是 GitLab 容器的特殊环境变量里面的配置会直接写进容器内的gitlab.rb文件启动时自动生效。shm_size这个容易忽略默认的 64M 共享内存太小GitLab 在跑 Git 操作时偶尔会报/dev/shm空间不足建议设成 256m。3.2 初始化流程与首次登录配置配置写好之后启动服务docker compose up -d第一次启动会比较慢因为 GitLab 需要初始化数据库、编译静态资源整个过程可能需要 3 到 5 分钟。你可以通过日志实时观察启动进度docker logs -f gitlab当你看到gitlab Reconfigured!或者Running gitlab-rake db:migrate相关的日志结束后服务基本就绪。此时访问http://服务器IP:8080浏览器会跳到密码设置页面。这里有个细节即使你在环境变量里设置了initial_root_password首次访问时页面还是可能会让你重新设置 root 密码这是正常现象按页面提示操作就行。首次登录 root 账号之后第一件事我就建议去改两个设置第一个是关闭公开注册功能。GitLab 默认允许任何人注册账号如果服务器暴露在公网上分分钟被垃圾账号塞满。路径在管理区域 → 设置 → 通用 → 注册应用取消勾选“启用注册”即可。第二个是关闭项目可见性。默认新建项目可能是公开的如果你不想让代码被搜索引擎收录就把默认项目可见性改成私有。3.3 管理员账号与二次验证root 是管理员账号建议第一时间开启两步验证。在个人设置里找到“双重验证”用手机上的认证 App 扫码绑定即可。这一步能有效防止账号泄露后被恶意篡改仓库。另外root 账号的密码策略也建议调高。路径在管理区域 → 设置 → 通用 → 登录与注册把密码最小长度设为 12 位以上打开密码定期过期功能。这些小配置看起来麻烦但在服务器暴露在公网的情况下能帮你少操很多心。4. 高频场景实操配置 SSH 拉取与 HTTPS 访问4.1 SSH 密钥配置与常见坑GitLab 装好之后团队成员最常遇到的就是 SSH 克隆和推送问题。这里我详细讲一下配置流程。在本地生成密钥对ssh-keygen -t ed25519 -C 你的邮箱example.com然后查看公钥内容cat ~/.ssh/id_ed25519.pub把输出的公钥复制到 GitLab → 个人设置 → SSH 密钥 → 添加新的密钥。 Key 类型选ED25519然后点击“添加密钥”。这里有一个非常典型的坑由于我们用2222:22做了端口映射本地克隆代码时不能直接git clone gitgitlab.example.com:group/project.git必须手动指定端口git clone ssh://gitgitlab.example.com:2222/group/project.git如果你不想每次敲端口可以在~/.ssh/config里配置Host gitlab.example.com HostName gitlab.example.com User git Port 2222 IdentityFile ~/.ssh/id_ed25519配置好之后再克隆时就可以直接写git clone gitgitlab.example.com:group/project.git了SSH 客户端会自动读取配置里的端口。注意在 GitLab 的GITLAB_OMNIBUS_CONFIG里gitlab_rails[gitlab_shell_ssh_port]必须设为 2222这个参数决定了 GitLab 在页面上显示给用户的 SSH 克隆地址。如果不设置用户看到的克隆地址会默认使用 22 端口导致复制下来的地址根本连不通。4.2 HTTPS 终端访问配置如果只是内网使用HTTP 就够了。但如果你的 GitLab 要对公网提供服务或者团队对安全性有要求就必须上 HTTPS。我这里分享两种方式。第一种是让你自己签发自签名证书适合内网环境。把证书和私钥放到/data/gitlab/config/ssl目录下然后在GITLAB_OMNIBUS_CONFIG里追加external_url https://gitlab.example.com:8443 nginx[redirect_http_to_https] true重启容器后生效。自签名证书的唯一缺点是客户端会有安全警告但内网使用可以接受。第二种是使用 Lets Encrypt 免费证书。GitLab 自带 ACME 申请工具但前提是你的域名必须能正常解析到服务器并且 443 端口能对外访问。配置方式external_url https://gitlab.example.com letsencrypt[enable] true letsencrypt[contact_emails] [adminexample.com]配置好之后GitLab 会自动完成证书申请和续期基本不用再手动干预。这也是我推荐的方式前提是你有域名。4.3 修改 external_url 后的迁移问题这里必须提醒一下如果你部署时先用 IP 访问后来改成域名或者改了一个端口GitLab 会有一些“残留记忆”。具体表现是老项目的 HTTP 克隆地址还显示旧的 IP 或旧端口。这是因为 Git 仓库里的config文件存了原始的 remote 地址不会自动更新。解决办法有两个一是让成员手动改 remote 地址二是在 GitLab 项目设置里批量替换 URL路径是项目 → 设置 → 通用 → 高级 → 移除项目 → 转移项目这里有修改路径和仓库 URL 的选项。我自己的经验是尽量在第一天就把external_url定死不要等数据多了再去换。真到换的时候除了改配置还要检查 CI/CD 变量里有没有硬编码旧的仓库地址。5. 备份、恢复与升级实践5.1 备份策略与执行脚本GitLab 的数据有多重要不用多说代码仓库没了整个团队的产出就没了。所以备份这件事必须从一开始就规划好。GitLab 容器内置了备份工具最简单的备份命令docker exec -t gitlab gitlab-backup create这个命令会把数据库和仓库数据打包成一个 tar 文件默认放在容器内的/var/opt/gitlab/backups目录对应宿主机就是/data/gitlab/data/backups。但光备份 GitLab 内部数据还不够配置文件也要备份。我的习惯是写一个脚本把 config 目录和 data 目录一起打包然后传到异地服务器或者对象存储。#!/bin/bash # gitlab-backup.sh BACKUP_DIR/data/backups DATE$(date %Y%m%d_%H%M%S) # 执行 GitLab 内部备份 docker exec -t gitlab gitlab-backup create # 打包配置和数据目录 tar -czf $BACKUP_DIR/gitlab_full_$DATE.tar.gz \ -C /data/gitlab/config . \ -C /data/gitlab/data . # 删除超过 7 天的本地备份 find $BACKUP_DIR -name *.tar.gz -mtime 7 -delete建议把这个脚本加到 crontab 里每天凌晨执行一次0 2 * * * /bin/bash /opt/scripts/gitlab-backup.sh /var/log/gitlab-backup.log 215.2 恢复操作与升级注意事项恢复备份的操作# 停止相关服务 docker exec -t gitlab gitlab-ctl stop puma docker exec -t gitlab gitlab-ctl stop sidekiq # 恢复备份文件名就是备份出来的 tar 包名不含路径和扩展名 docker exec -t gitlab gitlab-backup restore BACKUP1699999999_2024_11_15_16.10.2 # 重启容器 docker restart gitlab升级 GitLab 是另一个容易翻车的场景。我的建议是升级前一定先做全量备份然后小版本原地升大版本必须跳级升。比如从 16.0 升到 16.11 可以一次性升级但从 15.x 直接升到 16.x 就有风险官方文档要求必须先升级到 16.0.x再继续往上升。升级的操作本身很简单改一下docker-compose.yml里的镜像版本号然后docker compose pull gitlab docker compose up -d但升级后数据库迁移需要一点时间启动过程可能比平时慢。启动后要先访问一下页面确认项目、成员、CI 变量都在再让团队恢复日常使用。注意GitLab 的备份文件与版本是强相关的。16.x 版本备份出来的包不能直接恢复到 15.x 版本。这也是为什么我前面强调要锁版本号、控制升级节奏。6. 常见问题与排查技巧实录6.1 容器启动后一直重启这个我在刚上手时遇到过一次现象是容器启动后过几分钟就自动退出然后反复重启。排查步骤docker logs --tail 100 gitlab如果日志里有Permission denied相关报错基本可以确定是挂载目录的权限问题。GitLab 容器内部使用git用户运行UID 是 998所以宿主机上的挂载目录必须对 UID 998 有读写权限。解决办法chown -R 998:998 /data/gitlab这个坑务必要注意如果你用普通的root:root或者当前用户去挂载容器启动初始化阶段就会因为无法写配置而失败。6.2 克隆时提示 SSH 权限被拒绝gitgitlab.example.com: Permission denied (publickey,keyboard-interactive)遇到这个问题依次检查公钥是否已经添加到 GitLab 个人设置里本地 SSH 客户端实际使用的是哪个密钥文件端口映射是否正确ssh -T -p 2222 gitgitlab.example.com能否连通如果前两步都没问题大概率是端口的问题。用ssh -v调试模式看看具体卡在哪一步ssh -vT -p 2222 gitgitlab.example.com输出里会明确告诉你认证阶段用的哪个密钥、服务端是否接受。这种问题排查起来并不难关键是要养成看日志的习惯。6.3 镜像拉不下来或拉取超时前面提到过官方镜像比较大网络不好时经常卡在docker pull阶段。解决方式是配置镜像加速器。在/etc/docker/daemon.json里填镜像源地址然后重启 Docker 服务systemctl restart docker如果拉取中途失败可以多试几次Docker 支持断点续传。另外如果你在内网环境可以把镜像从一台能访问外网的机器上docker save导出再传到内网服务器docker load导入。6.4 服务器内存经常被占满GitLab 是一个比较吃资源的应用如果只有 4G 内存建议在GITLAB_OMNIBUS_CONFIG里调整这些参数puma[worker_processes] 2 sidekiq[max_concurrency] 5 postgresql[shared_buffers] 256MB gitaly[concurrency] 4 prometheus_monitoring[enable] false如果是小团队prometheus 监控可以直接关掉省不少内存。实测下来配置调整之后4G 内存基本够用内存占用稳定在 3G 左右。7. 联动 CI/CDGitLab Runner 与自动化部署7.1 Runner 注册与配置GitLab 装好的最大价值之一就是自带的 CI/CD 能力。要在 Docker 上搭建 GitLab 服务器的基础上用 CI/CD你需要额外部署一个 GitLab Runner。Runner 同样推荐容器化部署注册命令示例docker run -d --name gitlab-runner --restart always \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /data/gitlab-runner/config:/etc/gitlab-runner \ gitlab/gitlab-runner:latest然后进入容器注册docker exec -it gitlab-runner gitlab-runner register \ --url http://gitlab.example.com:8080 \ --token YOUR_REGISTRATION_TOKEN注册 token 在项目设置 → CI/CD → Runner 里可以找到。选 executor 类型时我建议选docker这样每个构建任务都是一个隔离的容器环境干净不会互相污染。7.2 一个简单的自动化部署流水线Runner 注册好之后在项目根目录创建.gitlab-ci.yml一个最基础的流水线stages: - build - deploy variables: IMAGE_TAG: $CI_COMMIT_SHORT_SHA build: stage: build image: docker:24.0 services: - docker:24.0-dind script: - docker build -t registry.example.com/myapp:$IMAGE_TAG . - docker push registry.example.com/myapp:$IMAGE_TAG only: - main deploy: stage: deploy image: alpine:latest before_script: - apk add --no-cache openssh-client script: - ssh deployyour-server docker pull registry.example.com/myapp:$IMAGE_TAG docker stop myapp docker rm myapp docker run -d --name myapp -p 3000:3000 registry.example.com/myapp:$IMAGE_TAG only: - main这个流水线的逻辑是代码推到 main 分支后自动构建 Docker 镜像并推送到仓库然后 SSH 到服务器拉取新镜像并重启容器。你可以根据自己的场景扩展比如加测试阶段、加多环境部署等。8. 写在最后的一些体会大概从第一台 GitLab 装到现在我用容器方式部署 GitLab 已经好几年了。最直观的感受是如果你把 GitLab 当成一个“必须长期稳定运行的基础设施”来运维那么 Docker 化确实让备份、还原、迁移、升级这些高频操作变得更可控。遇到问题的时候docker logs一把梭配合挂载出来的配置目录大部分问题都能在半小时内定位清楚。当然容器化不是银弹。如果团队规模很小只有几个人其实用 Gitea 或者直接把项目托管到云端代码平台会更省事。GitLab 的价值在于它把代码托管、Issue 管理、Code Review、CI/CD 集成到了一起适合那些需要自建、数据敏感的团队。最后再分享一个小技巧如果你的磁盘空间经常告急记得定期清理 Docker 的无用镜像和缓存。我一般一个月跑一次docker system prune -f docker image prune -f这套东西用顺手了你会觉得管理一套代码托管平台也不是什么难事。希望这篇文章能帮你少踩几个坑早日把 GitLab 顺利跑起来。