想在 VPS 上搭建 GitLab,先别急着复制一条 docker run。GitLab 把代码仓库、Merge Request、Issue、Container Registry 和 CI/CD 塞进了一套系统,代价就是比 Gitea 吃资源,也更依赖正确的域名、端口和备份。
本文使用官方 GitLab CE 19.2.1 镜像,通过 Docker Compose 部署。宿主机 SSH 继续使用 22,GitLab 的 SSH 克隆改走 2222;Web 端由 GitLab 内置 Nginx 和 Let's Encrypt 直接提供 HTTPS。Runner 放在另一台 VPS,避免构建任务拖垮代码仓库。
GitLab 官方给普通单节点的基线是 8 vCPU、16GB 内存;内存受限方案最低可以降到 2GB 内存加 1GB Swap,但限定在约 5 名开发者、小于 100MB 的仓库,并且可能明显变慢。实际选择可以这样看:
| 用途 | 建议配置 | 说明 |
|---|---|---|
| 临时体验、个人小仓库 | 2–4 vCPU、4GB 内存、40GB SSD | 需要低内存调优,不建议同时跑 Runner |
| 小团队日常使用 | 4 vCPU、8GB 内存、80GB NVMe | 更适合作为起步配置 |
| CI、Registry、制品较多 | 8 vCPU、16GB 以上 | Runner 和对象存储最好拆开 |
SSD/NVMe 的随机读写比单纯堆 CPU 更重要。还没确定配置,可以先看VPS 配置选择指南。如果只需要一个轻量私有 Git 仓库,不需要完整 DevOps 平台,Gitea 搭建教程通常更省钱。
磁盘也别只按仓库源码计算。数据库、LFS、Package、Container Registry、CI 日志和 Artifact 都会增长,备份时还需要额外临时空间。准备上线前先列出预计用户数、仓库总量、单次构建制品和保留天数,再给磁盘留出至少一轮完整备份的空间。磁盘爆满往往比 CPU 不够更先让 GitLab 停摆。
先把 gitlab.example.com 的 A/AAAA 记录解析到 VPS 公网地址。下面的域名和邮箱都要换成你自己的;Let's Encrypt 验证时,80 和 443 必须能从公网访问。
检查端口占用:
sudo ss -lntup | grep -E ':22|:80|:443|:2222'
22 应该仍由宿主机 SSH 使用,80、443 和 2222 留给 GitLab 容器。创建持久化目录:
sudo mkdir -p /srv/gitlab/{config,logs,data}
sudo mkdir -p /srv/gitlab-backups
配置 UFW 前先保持当前 SSH 会话,再开一个新终端测试,避免把自己锁在门外:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 2222/tcp
sudo ufw enable
sudo ufw status numbered
云厂商安全组也要放行相同端口。操作不熟时先看UFW 防止 SSH 锁死教程。
Ubuntu 或 Debian 安装 Docker 后,先确认服务和 Compose 插件可用:
sudo systemctl enable --now docker
docker --version
docker compose version
df -h /srv/gitlab
GitLab 官方不支持把这套生产示例直接搬到 Docker Desktop for Windows。正式服务器也不要把数据目录放到 NFS、EFS 等高延迟网络文件系统上;Gitaly 对随机 I/O 很敏感。
创建项目目录和 Compose 文件:
sudo mkdir -p /opt/gitlab
cd /opt/gitlab
sudo nano compose.yaml
写入以下配置:
services:
gitlab:
image: gitlab/gitlab-ce:19.2.1-ce.0
container_name: gitlab
restart: always
hostname: gitlab.example.com
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'https://gitlab.example.com'
gitlab_rails['gitlab_shell_ssh_port'] = 2222
letsencrypt['enable'] = true
letsencrypt['contact_emails'] = ['[email protected]']
ports:
- '80:80'
- '443:443'
- '2222:22'
volumes:
- /srv/gitlab/config:/etc/gitlab
- /srv/gitlab/logs:/var/log/gitlab
- /srv/gitlab/data:/var/opt/gitlab
shm_size: '256m'
logging:
driver: json-file
options:
max-size: '20m'
max-file: '5'
这里必须同时改 hostname、external_url、联系邮箱和 DNS。2222:22 是宿主端口到容器 SSH 端口的映射,而 gitlab_shell_ssh_port = 2222 负责让网页生成正确的 Clone URL,少写任何一处都会让用户连错端口。
先检查渲染结果,再启动:
docker compose config
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=200 gitlab
生产环境不要用 latest。固定补丁版本不仅避免重建时突然升级,恢复备份时也需要完全相同的 GitLab 版本和 CE/EE 类型。Compose 的健康检查、日志和回滚原则可参考Docker Compose 生产配置清单。
GitLab 首次初始化数据库和内部服务需要几分钟。容器显示 running 不等于页面已经可用,短时间看到 502 先检查进度:
docker exec -it gitlab gitlab-ctl status
docker compose logs --tail=200 gitlab
curl -I https://gitlab.example.com
若 Let's Encrypt 失败,先确认域名已在公共 DNS 生效,80/443 没被安全组拦截,也没有错误的 CAA 记录。不要用无法公开解析的 example.com 真去申请证书。
页面正常后读取临时 root 密码:
docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password
用户名是 root。登录后立即改密码、填写管理员邮箱,并创建日常使用的普通账号。这个临时密码文件会在首次重启后的 24 小时自动删除,不要把密码复制到脚本或聊天记录里。
先在 GitLab 新建一个空项目,再通过 HTTPS 推送一次。随后把本机 SSH 公钥添加到头像菜单的 Preferences → SSH Keys,并测试:
ssh -T -p 2222 [email protected]
首次连接会询问是否接受 Host Key,核对指纹后再确认。正确的 SSH 地址应类似:
ssh://[email protected]:2222/your-name/demo.git
如果页面仍显示 22,检查 gitlab_shell_ssh_port 拼写并重建容器;如果 2222 被拒绝,依次检查 docker compose ps、ss -lntup、UFW 和云安全组。不要为了省事把容器的 22 映射到宿主 22,否则会和管理 VPS 的 SSH 冲突。
4GB VPS 只适合个人和低频使用。可以把下面三行追加到 Compose 的 GITLAB_OMNIBUS_CONFIG,然后重建容器:
puma['worker_processes'] = 0
sidekiq['concurrency'] = 10
prometheus_monitoring['enable'] = false
docker compose up -d
docker exec -it gitlab gitlab-ctl status
free -h
docker stats --no-stream gitlab
单进程 Puma 会降低并发,Sidekiq 并发下降会让后台任务排队,关闭内置 Prometheus 后也少了监控能力。Swap 可以降低突发 OOM 风险,但一旦持续换页,GitLab 会非常慢。出现 Puma、Sidekiq 反复重启或内核 OOM 记录时,优先升级内存,而不是继续关闭关键服务。
GitLab Docker 镜像没有内置 SMTP 服务。没有邮件,邀请、重置密码、告警和备份通知都会受影响,应在 Omnibus 配置中接入可信 SMTP,并从 Rails Console 或管理后台发送测试邮件。
上线后至少完成这些设置:
- 不需要公开社区时关闭用户注册;
- 管理员和维护者启用双因素认证;
- 限制项目可见性和默认权限;
- 定期检查管理员、Access Token、Deploy Key 和 Runner;
- Runner 不与 GitLab 主机混跑,不给不受信任项目使用特权执行器。
还要检查 Admin Area 中的默认项目可见性、新用户权限和最大 Artifact 保留策略。公开注册如果确实需要开启,至少配合邮箱验证、管理员审批和频率限制。配置 SMTP 后不要只看保存成功,实际触发一次密码重置邮件,确认发件域名、退信和垃圾邮件策略都正常。
创建业务数据备份:
docker exec -t gitlab gitlab-backup create
ls -lh /srv/gitlab/data/backups
这个归档包含数据库、仓库、上传和制品等数据,但不等于整机备份。配置、gitlab-secrets.json、TLS 私钥、SSH Host Keys 仍在 /srv/gitlab/config,必须单独保存:
sudo tar -C /srv/gitlab -czf \
/srv/gitlab-backups/gitlab-config-$(date +%F).tar.gz config
sudo ls -lh /srv/gitlab-backups
如果使用外部对象存储,gitlab-backup 也不会替你复制 Bucket。把两类备份同步到另一台服务器或对象存储,并定期做恢复演练;只看到备份文件不算可恢复。完整思路见VPS 备份恢复演练。
建议保留“每日、每周、升级前”三类恢复点,并记录每个备份对应的 GitLab tag。同步完成后在远端校验文件大小或校验和,避免本机磁盘损坏时连备份一起丢失。Registry、LFS 或 Artifact 使用外部 Bucket 时,业务备份和对象存储快照要处于同一个维护窗口,减少恢复后的引用不一致。
恢复前要准备一套正常运行、版本与 Edition 完全相同的新 GitLab。停止 Puma 和 Sidekiq 后再执行恢复:
docker exec -it gitlab gitlab-ctl stop puma
docker exec -it gitlab gitlab-ctl stop sidekiq
docker exec -it gitlab gitlab-backup restore BACKUP=REPLACE_WITH_BACKUP_ID
docker restart gitlab
docker exec -it gitlab gitlab-rake gitlab:check SANITIZE=true
恢复会改写数据库,只能在维护窗口或隔离的恢复演练机上运行。恢复前还要还原匹配的配置和 Secrets。
升级前记录当前版本、完成备份,并查看 GitLab 官方 Upgrade Path。跨多个小版本或大版本时要经过指定停靠版本;GitLab 19 的停靠点包括 19.2、19.5、19.8 和 19.11,而且每一步都要等待后台迁移完成。
同一小版本升级补丁时,先把 Compose 中的镜像改为目标固定 tag,再执行:
docker compose pull
docker compose up -d
docker compose logs --tail=200 gitlab
docker exec -it gitlab gitlab-rake gitlab:check SANITIZE=true
随后验证网页、SSH 推送、Runner 和后台迁移。数据库已经迁移后,不能只把镜像 tag 改回旧版完成回滚;正确回滚要恢复旧镜像、旧配置和对应版本的备份。
Runner 执行的是项目里的代码,最好放到独立 VPS。先在 GitLab 的项目、群组或管理员 Runner 页面创建 Runner,拿到以 glrt- 开头的 Runner Authentication Token。旧 Registration Token 流程已经弃用,不要照搬旧教程。
在 Runner VPS 上启动固定版本容器:
sudo mkdir -p /srv/gitlab-runner/config
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:alpine-v19.2.1
docker exec -it gitlab-runner gitlab-runner register \
--non-interactive \
--url 'https://gitlab.example.com' \
--token 'glrt-REPLACE_WITH_RUNNER_AUTH_TOKEN' \
--executor docker \
--docker-image alpine:3.22 \
--description 'vps-docker-runner'
用最小 .gitlab-ci.yml 验证:
stages: [test]
runner-check:
stage: test
image: alpine:3.22
script:
- echo "GitLab Runner is working"
- uname -a
挂载 Docker Socket 意味着 Runner 能控制宿主 Docker 守护进程,这不是强隔离。只让可信项目使用,公共项目不要共享这个 Runner。需要缓存、构建镜像和更细的安全隔离时,可对照GitHub Actions 自托管 Runner 指南理解同类风险。
| 现象 | 先检查什么 | 常见原因 |
|---|---|---|
| 页面长期 502 | gitlab-ctl status、容器日志、内存 | 初始化未完成、OOM、内部服务失败 |
| HTTPS 证书申请失败 | DNS、80/443、CAA | 域名未生效或验证端口被拦截 |
| SSH 地址仍显示 22 | GITLAB_OMNIBUS_CONFIG | 没设置或没应用 gitlab_shell_ssh_port |
| 2222 连接被拒绝 | Compose 端口、UFW、安全组 | 端口未映射或防火墙漏放行 |
| Runner 一直 offline | Runner 日志、URL、证书 | URL 错、Token 错或证书链不可信 |
| Job 一直 pending | Runner scope、tag、暂停状态 | 项目没有可匹配的在线 Runner |
| 备份无法恢复 | 版本、CE/EE、Secrets | 目标版本不一致或配置备份缺失 |
通用诊断顺序是先看资源,再看容器状态,最后看对应服务日志:
free -h
df -h
docker compose ps
docker compose logs --tail=200 gitlab
docker exec -it gitlab gitlab-ctl tail
docker logs --tail=200 gitlab-runner
- 需要代码托管、MR、权限、Registry 和 CI/CD 一体化,选 GitLab,但准备足够内存和维护时间。
- 只要轻量私有 Git、Issue 和基础协作,选 Gitea,低配 VPS 更轻松。
- 代码继续放 GitHub,只想掌控构建机器,选择 GitHub Actions 自托管 Runner。
先在非关键 VPS 完成四件事:HTTPS 登录、SSH 推送、最小 CI Job、异机恢复。四项都跑通,再迁移正式仓库;否则“容器能启动”离可用的 GitLab 还差得很远。
