
说到GitLab很多团队一开始以为只是装个代码托管服务结果后面越用越深项目管理、代码评审、CI/CD全往里塞。我从最早在虚拟机里装GitLab CE到后来用Docker维护公司内部平台前后踩了不少坑。这篇文章就把我实际搭建和日常使用中总结的东西完整写一遍从安装方式选型、Docker部署、SSH配置、创建项目到Runner注册、CI/CD自动构建部署最后把常报错的几个问题集中排查一下。无论你是准备在本地服务器搭一个给小组用还是公司内网要长期维护照着做基本都能跑起来。1. 先把GitLab的坑摸清楚方案选型与整体思路1.1 团队规模不同替代方案怎么选经常有人问我GitHub私有仓库不也能用吗为什么还要自己搭GitLab说句公道话如果团队只有三五个开发者GitHub免费额度确实够用买不买套餐看人数。可一旦项目多了、成员多了私有仓库额度、代码评审权限、CI/CD分钟数这些限制就很烦人。自建GitLab最大的好处是代码完全在自己的服务器上不限仓库数、不限成员数、CI/CD运行次数也没人卡你。那为什么不选Gitea或者Gogs这类轻量级方案它们安装确实更省资源一个二进制文件就能跑适合非常小的团队。但到了几十人、上百人的规模Gitea在权限模型、审计日志、内置容器镜像仓库、安全补丁响应速度这些方面还是比GitLab弱一些。GitLab CE虽然资源占用偏高但胜在功能完整项目管理、Issue跟踪、MR评审、Wiki、CI/CD、Container Registry全都有一套系统能把研发流程里的大部分工具串起来。还有一个现实问题很多公司出于信息安全要求代码不允许放到外部平台或者需要对接内部的统一认证系统。GitLab支持LDAP、SAML社区版也支持LDAP这就成了私有化托管最稳妥的选择之一。1.2 为什么我推荐用Docker方式安装GitLab官方提供了多种安装方式源码编译、Omnibus安装包、Docker镜像、Helm Chart。个人用、中小团队用我基本上只推荐Docker Compose或者Omnibus。Omnibus安装包适合不想用容器、希望直接在一台干净机器上装成系统服务的场景。它的好处是依赖管理简单gitlab-ctl reconfigure一条命令就能改配置。缺点也很明显升级要下载几百MB的rpm/deb包回滚比较麻烦一旦服务器系统环境出了问题整个GitLab都跟着遭殃。Docker安装的最大优势是隔离和可迁移。GitLab所有配置、数据都放在挂载的卷目录里备份时只要把几个目录打包走换服务器时把数据卷迁过去再启动容器版本一致的话基本无缝。而且容器启动参数和环境变量能很清楚地表达出配置了什么比改一大段gitlab.rb直观很多。唯一要注意的是Docker方式下容器内的进程已经做了资源隔离对宿主机的要求其实更高一点。GitLab CE刚启动时就会占掉1GB多内存跑一段时间稳定后大约在2GB到3GB如果还开了Runner构建任务4GB内存的机器会非常紧张。所以我一般建议服务器至少4核8GB起步磁盘能用SSD就别省GitLab对IO还是挺敏感的。1.3 部署前最重要的网络与端口规划很多人装完GitLab打开页面发现克隆地址不对、SSH连不上大部分原因不是软件装坏了而是端口和external_url没规划好。GitLab默认对外提供三个端口HTTP的80、HTTPS的443、SSH的22。但在真实服务器上80和443往往已经被Nginx或其他服务占用了22也通常是系统SSH登录端口不能随便让给GitLab。我的习惯是宿主机用两个高位端口做映射比如20080映射到容器802222映射到容器22。这样既不跟现有服务冲突防火墙规则也好写。同时在GitLab配置里必须设置external_url为浏览器访问的地址这个地址会直接影响页面上展示的克隆地址。如果外部访问域名是gitlab.example.com:20080就要写成http://gitlab.example.com:20080。SSH端口则通过gitlab_rails[gitlab_shell_ssh_port]告诉GitLab“对外SSH端口是2222”这样页面上的SSH克隆地址才会带端口不然生成的是gitgitlab.example.com:group/project.git直接连默认22端口自然连不上。2. 手把手Docker安装GitLab从零到能访问2.1 检查服务器环境先把基础环境准备好。我用的是Ubuntu 22.04作为示例CentOS 7/8也能跑无非是包管理命令不同。检查Docker是否安装docker --version docker compose version如果还没有Docker直接用官方脚本安装注意别在没确认脚本内容前就执行陌生脚本官方脚本地址是https://get.docker.com/curl -fsSL https://get.docker.com | bash国内网络环境下拉取镜像可能会慢可以在/etc/docker/daemon.json里配置镜像加速源然后重启Docker{ registry-mirrors: [https://docker.m.daocloud.io] }systemctl restart docker接着创建GitLab的数据目录。我习惯统一放在/srv/gitlab下mkdir -p /srv/gitlab/{config,logs,data}这三个目录分别对应GitLab的配置、日志和应用数据之后备份也主要盯这几个目录。2.2 编写compose文件并启动在/srv/gitlab下新建docker-compose.ymlversion: 3.6 services: gitlab: image: gitlab/gitlab-ce:16.11.2-ce.0 container_name: gitlab restart: always hostname: gitlab.example.com environment: GITLAB_OMNIBUS_CONFIG: | external_url http://gitlab.example.com:20080 gitlab_rails[time_zone] Asia/Shanghai gitlab_rails[gitlab_shell_ssh_port] 2222 ports: - 20080:80 - 2222: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中的域名保持一致避免内部生成链接时不一致。external_url一定写成用户最终访问的地址。如果暂时没有域名也可以用http://192.168.1.10:20080这种IP加端口的形式但后面改域名时记得同步改配置。gitlab_rails[gitlab_shell_ssh_port] 2222是很多人漏掉的一项没有它SSH克隆地址不会带端口。shm_size设置成256m主要是防止页面加载和部分操作时报共享内存不足。启动cd /srv/gitlab docker compose up -d首次启动要等很久因为容器内部要做初始化、配置数据库、启动一系列组件。这时候可以看日志docker logs -f gitlab看到类似gitlab Reconfigured!、puma: master ready之类的日志就说明基本起来了。整个过程在我的服务器上大约3到5分钟配置差一些的机器可能要十分钟以上。2.3 首次登录、改密码与关闭注册GitLab 16.x之后首次启动会生成一个临时root密码存放在容器内的/etc/gitlab/initial_root_password文件中。拿到它docker exec -it gitlab grep Password: /etc/gitlab/initial_root_password浏览器打开http://gitlab.example.com:20080用户名为root密码就是上面看到的字符串。这个文件在24小时后会被自动删除所以一定尽早登录并改密码。如果超过24小时没登录别急还有两条路一条是把容器内这个文件的内容再生成出来太绕更直接的是进入容器用Rails控制台改docker exec -it gitlab bash gitlab-rails console进入Ruby交互环境后执行u User.find_by_username(root) u.password 你的新密码 u.password_confirmation 你的新密码 u.save!看到true返回值就说明改成功了exit退出。接下来强烈建议做两件事。一是关闭公开注册不然任何人都能去你的页面上注册账号非常危险。路径是左侧菜单栏最底部Admin Area→Settings→Sign-up restrictions取消勾选Sign-up enabled保存。二是设置一个普通的开发账号平时不要用root来提交代码和做常规操作root只用于管理后台。3. GitLab日常使用实操克隆、上传与权限管理3.1 配置SSH密钥免密拉代码SSH密钥是最推荐的认证方式比每次输HTTP密码舒服太多。生成密钥时用ed25519算法兼容性已经足够好ssh-keygen -t ed25519 -C 你的邮箱example.com一路回车生成的公钥默认在~/.ssh/id_ed25519.pub。复制公钥内容cat ~/.ssh/id_ed25519.pub然后在GitLab网页右上角点头像选Preferences→ 左侧SSH Keys把公钥粘贴到Key一栏Title会自动识别点Add key。验证是否配好ssh -T gitgitlab.example.com -p 2222看到Welcome to GitLab, username!就说明成功了。需要注意的是如果SSH不是默认22端口ssh -T要带上-p 2222或者直接按照网页上克隆URL里的方式连接。3.2 创建项目并推送代码到仓库在GitLab左上角点New project选Create blank project。填项目名可见性建议选Private除非你确实需要公开给外部。创建完成页面会显示各种克隆地址比如SSHgitgitlab.example.com:2222/root/demo.gitHTTPhttp://gitlab.example.com:20080/root/demo.git本地初始化并推送echo # demo README.md git init git add README.md git commit -m first commit git branch -M main git remote add origin gitgitlab.example.com:2222/root/demo.git git push -u origin main如果你之前已经用gitee或GitHub本地git config user.name和user.email可能不对GitLab提交记录里会显示别人的头像。检查一下git config --global --list不对就设置成自己的git config --global user.name 你的名字 git config --global user.email 你的邮箱拉取已有项目到本地也简单git clone gitgitlab.example.com:2222/group/project.git需要注意如果项目在Group下面SSH URL里会多一层Group路径。权限模型上Group下面套Project可以给不同成员设置Owner、Maintainer、Developer等角色我建议养成用Group管理项目的习惯而不是全堆在个人名下否则后续权限控制会很乱。3.3 Web端高频功能与权限注意点Web端有一个很实用的功能是页面上传文件适合临时加个文档或配置。进入仓库后点Add→Upload file选择文件后提交。我一般只建议上传小文件超过几十MB的东西尽量用Git LFS或者干脆不要把二进制大文件放进Git仓库。权限管理的核心在Settings→Members。给成员分配角色时注意Guest只能看Issue和Wiki无法访问代码Reporter可以拉代码但不允许推送Developer可以正常开发推送但不能改仓库设置Maintainer管理权限较大Owner拥有全部权限。日常开发给到Developer最合适只有核心维护者才给Maintainer。不要全网默认开启LFSGitLab CE版也支持LFS但需要在Admin Area里启用。之前遇到过团队直接上传几百MB的压缩包到Git仓库导致仓库体积爆炸后来加上LFS后才解决。大文件用LFS代码库才不至于越滚越大。4. CI/CD自动化Runner注册与一个可落地的流水线4.1 为什么优先用GitLab CI而不是另搭一套GitLab本身集成CI/CD的体验非常顺你不需要额外部署Jenkins只要注册一个Runner然后在仓库里写.gitlab-ci.yml就能跑起来。Runner负责执行JobGitLab服务端负责调度和展示结果。对大多数团队来说一套GitLab Runner就足够支撑日常的构建、测试、部署。很多人纠结Jenkins和GitLab CI怎么选。我的看法是如果团队已经深度使用GitLab那优先用GitLab CI因为MR、Commit、Pipeline之间的关联是天生的代码改了触发哪个流水线一眼就能看懂。Jenkins则更适合已有大量插件、已有较多自定义构建逻辑的老团队。再说直接一点GitLab CI的配置文件是放在仓库里的改构建流程要走代码评审这个审计习惯很好Jenkins的Job配置在服务端少了这层约束。4.2 Runner注册docker executor与共享/项目RunnerRunner有三种层级Shared Runner整个实例共享、Group Runner组内共享、Project Runner项目专用。个人学习用Project Runner就够团队统一管理用Group Runner更合适。注册Runner前先拿到注册token。项目入口Settings→CI/CD→Runners→ 展开New project runner页面会看到注册命令和token。我用的是Runner容器方式注册时先临时跑一次交互式命令docker run --rm -it \ -v /srv/gitlab-runner/config:/etc/gitlab-runner \ gitlab/gitlab-runner:latest register按提示输入GitLab地址比如http://gitlab.example.com:20080、token、Runner描述、标签tag、executor类型。executor我选docker默认镜像填alpine:latest。注册完成后正式启动Runner容器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.sock这样Runner才能在宿主机上动态创建执行构建的容器这种模式叫docker executor。注册完如果Runner列表里显示online就说明连通了。需要注意如果项目配置GitLab CI时没有指定tag而Runner注册时又带了tag那么默认不会接收Job。两种解法要么在.gitlab-ci.yml里用tags关键字指定Runner的tag要么在Runner配置中勾选Run untagged jobs。4.3 一个能用的.gitlab-ci.yml模板下面是实际项目里常用的两阶段流水线构建Docker镜像然后SSH登录服务器拉取镜像并启动容器。因为要登录镜像仓库和服务器密钥都通过GitLab CI/CD变量注入不写死在仓库里。stages: - build - deploy variables: IMAGE_NAME: ${CI_REGISTRY}/demo/app:${CI_COMMIT_SHORT_SHA} build: stage: build image: docker:24.0.7 services: - docker:24.0.7-dind before_script: - echo $CI_REGISTRY_PASSWORD | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin script: - docker build -t $IMAGE_NAME . - docker push $IMAGE_NAME only: - main deploy: stage: deploy image: alpine:latest before_script: - apk add --no-cache openssh-client - eval $(ssh-agent -s) - echo $DEPLOY_SERVER_PRIVATE_KEY | tr -d \r | ssh-add - - mkdir -p ~/.ssh - chmod 700 ~/.ssh script: - ssh -o StrictHostKeyCheckingno rootserver.example.com docker pull $IMAGE_NAME docker stop app || true docker rm app || true docker run -d --name app -p 8080:80 $IMAGE_NAME only: - main when: manual几个变量在GitLab里配置CI_REGISTRY_USER、CI_REGISTRY_PASSWORD是镜像仓库的账号密码DEPLOY_SERVER_PRIVATE_KEY是部署服务器私钥。配置路径Settings→CI/CD→Variables。注意私钥要选择File类型或直接复制多行文本粘贴时不要把换行弄丢。when: manual让部署阶段变成手动点击避免每次合并分支都自动重新部署生产环境一般不希望自动发布。在GitLab的Pipeline页面右上角会有一个Play按钮点一次就跑部署。4.4 和Jenkins联动时Connection失败怎么处理有些团队最终选择Jenkins做调度GitLab只做代码仓库就会出现Jenkins配置GitLab Connection时的报错Login failed. Check API token or GitLab version. Log in via Git if the version is older...这个报错十有八九是下面几个原因一是没有用Access Token而是填了用户名密码。Jenkins的GitLab插件要求使用个人访问令牌或者项目访问令牌。在GitLab里生成右上角头像 →Preferences→Access Tokens勾选api权限生成后复制。注意token只显示一次刷新页面就没了。二是GitLab版本太旧。老版本GitLab走的是v3 API新插件默认用v4版本不匹配就会认证失败。建议把GitLab升级到官方还在支持的版本既能兼容插件还能避免很多已知安全漏洞。三是Jenkins里填的URL不对。Host URL应该填GitLab根地址比如http://gitlab.example.com:20080不要带上/api/v4。Credential类型选择GitLab API token然后把token粘贴进去。关于热词里提到的“没有gitlab yaml依然触发runner是否可行”这个要说清楚GitLab Runner本身不会凭空生成Pipeline它只执行服务端派发下来的Job而Job必须来自某个CI配置。如果仓库里没有.gitlab-ci.yml正常情况下不会产生Pipeline。如果你发现Runner还是被触发了去查看项目Settings→CI/CD→General pipelines里的CI/CD configuration file字段可能被指向了别的文件或者仓库里通过include引用了其他模板。还有共享CI模板也可能来自父仓库检查一下项目根目录是否确实存在隐藏的CI配置。5. 常见报错排查与安全加固5.1 clone地址不对先查external_url经常有人问为什么页面上显示的克隆地址是服务器内部主机名而不是我想要的域名本质原因就是external_url设置和用户实际访问地址不一致。Docker启动时环境变量里的external_url写的是什么网页上生成的克隆地址就是什么。如果已经启动后才发现写错了修改方式是在docker-compose.yml的GITLAB_OMNIBUS_CONFIG里改掉external_url再重启容器docker compose down docker compose up -dGitLab重启时会重新执行reconfigure自动更新Nginx配置和Web页面上的克隆地址。如果你是Omnibus包安装那就是改/etc/gitlab/gitlab.rb中的external_url然后执行gitlab-ctl reconfigure。还有一类坑反向代理没有配好。你用Nginx代理GitLab时Nginx要把Host请求头原样传给后端否则GitLab会认为访问域名不对生成重定向时跳错。比如server { listen 80; server_name gitlab.example.com; location / { proxy_pass http://127.0.0.1:20080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }如果用了TLS还需要处理https跳转GitLab配置里external_url也需要改成https://gitlab.example.com。5.2 一台电脑同时用GitHub和公司GitLab很多人本地电脑既要拉GitHub开源项目又要拉公司GitLab仓库SSH密钥如果都用默认的id_ed25519会互相冲突。解决办法是生成多把密钥然后在~/.ssh/config里做区分。先分别生成密钥ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_github -C github邮箱 ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_gitlab -C 公司邮箱把两个公钥分别添加到GitHub和GitLab后台。然后编辑~/.ssh/configHost github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519_github Host gitlab.example.com HostName gitlab.example.com Port 2222 User git IdentityFile ~/.ssh/id_ed25519_gitlab这样git在连接不同域名时会自动选用对应密钥。测试ssh -T gitgithub.com ssh -T gitgitlab.example.com -p 2222这里有个细节要注意GitLab如果走非22端口网址里的端口是加在域名后面的但配置里Port还是单独写不要写进HostName里。5.3 API Token认证失败除了Jenkins很多工具比如IDE插件、脚本也需要用API Token连接GitLab。报错信息除了上面提到的Login failed还可能是401 Unauthorized。排查顺序先核对token权限。个人访问令牌在创建时有很多scope调用API至少需要api这个scope只读操作可以用read_api但如果工具需要创建用户、改设置就得用api。第二是token是否过期GitLab允许给token设置有效期过期后要重新生成。第三是网络问题GitLab实例开了CDN或者反向代理后API请求被拦截优先查反向代理日志。一个不太常见但实际会踩的坑GitLab实例如果关闭了密码登录只允许LDAP/SSO有些工具用用户名密码形式获取token也会失败。这时应该在GitLab页面上手动生成Access Token再用token做集成。5.4 安全加固与版本升级建议GitLab属于全球部署广泛的基础设施官方每个月都会发布安全版本很多高危漏洞的修复方案直接说就是“升级到包含补丁的版本”。我的建议是不要长期停留在老版本至少在官方公告发布后一个月内完成升级。升级前务必备份GitLab的备份可以简单理解成把/srv/gitlab/config、/srv/gitlab/data和/srv/gitlab/logs这几个目录打包。Docker方式升级版本时把image标签改成新版本号然后docker compose pull docker compose up -dGitLab会自动执行迁移。如果之前只是小版本升级通常不会有大问题跨大版本升级前一定要看官方升级路径有些版本不能直接跳到最新版需要先升到某个过渡版本。安全方面还有几个基础动作关闭公开注册给所有账号强制设置强密码最好开启2FA限制GitLab管理后台的访问IP只允许公司网段访问/admin路径私有项目不要随手改成公开不用的账号及时停用。备份文件要加密存放因为GitLab数据里包含源码泄露了影响比服务器宕机还大。最后想再啰嗦两句这套环境我在公司内部跑了两年多最大的体会是GitLab的复杂性不在安装而在使用习惯。比如端口映射、external_url这种配置一开始没规划好后面改起来会牵扯到所有开发者本地仓库的remote地址。如果从头开始搭建议先把域名、端口、权限模型想清楚再动手。第一次用的话可以先用测试项目跑通SSH、HTTP克隆再上CI/CD别一上来就把老项目接进来容易手忙脚乱。等基础流程通了后面所有团队的代码托管、自动化构建部署就都顺理成章了。