GitLab CE в Docker Compose: готовый файл
Когда команда вырастает из GitHub-бесплатки или упирается в лимиты приватных репозиториев, логичный шаг — поднять свой GitLab. Self-hosted GitLab CE закрывает сразу три задачи: git-репозитории, CI/CD-пайплайны и Container Registry — без ежемесячной платы за место и без чужих серверов между вашим кодом и продакшеном. Ниже — рабочий docker-compose.yml, с которым GitLab поднимается за один запуск и не падает от нехватки памяти на первом же git push.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Что нужно на сервере
GitLab CE — тяжёлое приложение: внутри он тащит Puma (Rails-сервер), Sidekiq (фоновые задачи), Gitaly (работа с git-объектами), Redis, PostgreSQL, Nginx и Prometheus для метрик. Даже в минимальной конфигурации это ощутимо больше, чем у условного Gitea.
Официальный минимум от GitLab — 4 vCPU и 4 ГБ RAM для команды до 20 пользователей, но на практике sidekiq и предзагрузка Rails-приложения съедают память рывками, и на 4 ГБ система нередко уходит в OOM при первом же gitlab-ctl reconfigure или крупном импорте репозитория. Реалистичный старт:
| Сценарий | vCPU | RAM | Диск |
|---|---|---|---|
| Соло / 2-3 разработчика | 2 | 6 ГБ | 40 ГБ SSD |
| Команда до 10 человек | 4 | 8 ГБ | 80 ГБ SSD |
| Команда 10-30 человек + CI-раннеры на этом же хосте | 6-8 | 16 ГБ | 150+ ГБ SSD |
Если планируете гонять CI/CD-джобы на том же сервере, закладывайте RAM с запасом — каждая параллельная джоба с Docker-in-Docker откусывает свой кусок памяти сверх самого GitLab.
Диск важен не меньше CPU: репозитории, артефакты сборок, LFS-объекты и Container Registry со временем разрастаются, а GitLab плохо переживает нехватку места на разделе с данными — база может начать отказывать в записи. Под такую нагрузку разумно сразу арендовать VPS с NVMe и возможностью нарастить диск без переустановки.
Готовый docker-compose.yml
Официальный образ gitlab/gitlab-ce — это по сути весь Omnibus-дистрибутив в контейнере: внутри уже нужный Ruby, PostgreSQL-клиент и вся обвязка. Отдельные контейнеры для базы и Redis в стандартной поставке не нужны — они запускаются внутри самого образа через runit. Это упрощает docker-compose.yml, но означает, что весь стек живёт и падает как единое целое.
Создайте рабочую директорию и файл:
mkdir -p /opt/gitlab/{config,logs,data}
cd /opt/gitlab
nano docker-compose.yml
services:
gitlab:
image: gitlab/gitlab-ce:17.3.2-ce.0
container_name: gitlab
restart: unless-stopped
hostname: 'git.example.com'
shm_size: '256m'
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'https://git.example.com'
gitlab_rails['gitlab_shell_ssh_port'] = 2222
nginx['listen_port'] = 80
nginx['listen_https'] = false
letsencrypt['enable'] = false
prometheus_monitoring['enable'] = false
gitlab_rails['gitlab_email_from'] = 'gitlab@example.com'
gitlab_rails['gitlab_email_reply_to'] = 'noreply@example.com'
ports:
- '127.0.0.1:8080:80'
- '2222:22'
volumes:
- './config:/etc/gitlab'
- './logs:/var/log/gitlab'
- './data:/var/opt/gitlab'
networks:
- gitlab-net
networks:
gitlab-net:
driver: bridge
Ключевые моменты:
- Версия образа зафиксирована (
17.3.2-ce.0), а неlatest. GitLab выпускает мажорные обновления с обязательной последовательностью миграций — случайный переход через несколько версий наlatestпри перезапуске контейнера может сломать базу. Обновляйтесь осознанно, читая changelog. external_urlдолжен совпадать с реальным доменом — GitLab использует его для генерации ссылок в письмах, webhook'ах и клонировании по HTTPS. Поменять его постфактум можно, но потребуетgitlab-ctl reconfigure.- SSH-порт 2222, а не 22 — если на хосте уже есть системный SSH-демон на 22-м порту (а он почти всегда есть), контейнер на него просто не встанет. Разработчики при клонировании по SSH указывают порт явно:
git clone ssh://git@git.example.com:2222/group/repo.git. nginx['listen_https'] = false— TLS отдаём внешнему reverse-прокси (см. следующий раздел), сам GitLab слушает голый HTTP на 8080 только на loopback-интерфейсе.shm_size: 256m— по умолчанию Docker выделяет контейнеру всего 64 МБ/dev/shm, а PostgreSQL внутри GitLab на этом задыхается при параллельных запросах.
Запуск:
docker compose up -d
docker compose logs -f gitlab
Первый старт занимает 3-5 минут — внутри разворачивается PostgreSQL, применяются миграции, компилируются ассеты. Готовность проверяется командой:
docker exec -it gitlab gitlab-ctl status
Когда все сервисы (puma, sidekiq, postgresql, redis, gitaly, nginx) в статусе run, GitLab готов принимать запросы.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверHTTPS через внешний reverse-прокси
Встроенный Let's Encrypt в образе GitLab работает, но плохо уживается с ситуацией, когда на сервере уже крутятся другие сайты или сервисы на 80/443 порту. Практичнее вынести TLS на отдельный reverse-прокси — Traefik или Caddy — и проксировать на внутренний порт 8080.
Пример для Caddy (Caddyfile):
git.example.com {
reverse_proxy 127.0.0.1:8080
}
git.example.com:2222 {
# SSH проксируется отдельно на уровне TCP, см. ниже
}
Caddy сам выпустит и продлит сертификат — подробности настройки описаны в статье про Caddy с авто-SSL на сервере. Если предпочитаете Traefik с его лейблами и dashboard, логика та же: контейнер GitLab публикует 8080 только на loopback, а Traefik берёт на себя TLS-терминацию и маршрутизацию — базовая настройка разобрана в статье про Traefik на Ubuntu 24.04.
Важный нюанс: SSH-клонирование (порт 2222) через HTTP-прокси не проксируется — это TCP-трафик, не HTTP. Прокидывайте порт напрямую через docker-compose.yml (как в примере выше) или через stream-блок в Nginx / TCP-роутер в Traefik, если хотите держать порт 22 снаружи, а 2222 только внутри Docker-сети.
Первый вход и настройка
Пароль root-пользователя при первой установке генерируется автоматически и лежит в контейнере 24 часа:
docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password
Сразу после первого входа под root смените пароль и создайте отдельного администратора — учётку root разумно использовать только для служебных задач, а не для повседневной работы.
Дальше стоит сразу пройтись по настройкам, которые проще выставить сейчас, чем чинить постфактум:
- Admin Area → Settings → General → Sign-up restrictions — по умолчанию self-registration часто включена. Если GitLab смотрит наружу, выключите открытую регистрацию или ограничьте её доменом почты.
- Admin Area → Settings → CI/CD → Continuous Integration and Deployment — здесь задаётся
gitlab_ci_default_git_depthи лимиты артефактов, чтобы CI-джобы не разрастались на диске бесконтрольно. - Preferences → SSH Keys — проверьте, что shell-порт 2222 действительно указан в разделе "Clone" интерфейса, иначе разработчики будут копировать команду клонирования без порта и получать
Connection refused.
Резервное копирование
GitLab включает встроенный механизм бэкапа, который архивирует репозитории, базу, вложения и CI-артефакты (но не конфигурацию /etc/gitlab и не SSH-ключи хоста — их бэкапят отдельно).
docker exec -it gitlab gitlab-backup create BACKUP=manual
Файл появится в /opt/gitlab/data/backups на хосте (том data смонтирован именно туда). Конфигурацию и ключи копируйте отдельно:
tar czf gitlab-config-$(date +%F).tar.gz -C /opt/gitlab config
Для регулярного расписания добавьте в cron на хосте:
0 3 * * * docker exec gitlab gitlab-backup create BACKUP=daily CRON=1 >> /var/log/gitlab-backup.log 2>&1
Опция CRON=1 подавляет прогресс-вывод, оставляя в логе только ошибки. Старые бэкапы GitLab сам не чистит — добавьте find /opt/gitlab/data/backups -mtime +14 -delete в тот же cron-джоб, иначе диск со временем забьётся архивами. Восстанавливать бэкап нужно на ту же мажорную версию GitLab, на которой он создавался — иначе миграции базы могут разойтись со структурой архива.
Если на сервере уже настроен более общий механизм бэкапа именованных Docker-томов, стоит свериться с типовыми граблями — они разобраны в статье про бэкап Docker volume на сервере: снимать снапшот тома data "на живую" без остановки Sidekiq рискованно — файл может получиться неконсистентным.
CI/CD-раннеры
Сам GitLab CE выполнять джобы не умеет — за это отвечает отдельный компонент gitlab-runner, который регистрируется в вашем инстансе и забирает задачи по очереди. Поднять раннер рядом с GitLab, в отдельном контейнере:
gitlab-runner:
image: gitlab/gitlab-runner:v17.3.1
container_name: gitlab-runner
restart: unless-stopped
volumes:
- './runner-config:/etc/gitlab-runner'
- '/var/run/docker.sock:/var/run/docker.sock'
networks:
- gitlab-net
Регистрация раннера (токен берётся в Admin Area → CI/CD → Runners вашего GitLab):
docker exec -it gitlab-runner gitlab-runner register \
--non-interactive \
--url "https://git.example.com" \
--registration-token "ВАШ_ТОКЕН" \
--executor "docker" \
--docker-image "docker:24-dind" \
--docker-privileged \
--description "docker-runner"
Флаг --docker-privileged нужен, если джобы сами собирают Docker-образы (Docker-in-Docker) — это открывает раннеру расширенные привилегии на хосте, поэтому такой раннер разумно держать на отдельном сервере, а не на машине с продакшен-нагрузкой. Если джобы не собирают образы, а просто гоняют тесты — привилегированный режим не нужен, executor docker без флага безопаснее.
Монтирование docker.sock внутрь раннера означает, что раннер получает доступ к Docker-демону хоста — удобно, но любая джоба в таком раннере технически может выйти за пределы своего контейнера. Для чувствительных проектов стоит смотреть в сторону изолированных executor'ов (Kubernetes, отдельные VM) — здесь достаточно знать про этот компромисс.
Container Registry
Встроенный Registry хранит Docker-образы, собранные в CI, прямо рядом с репозиторием — удобно, но требует отдельного домена или поддомена и своего TLS-сертификата, потому что Docker-клиент не умеет ходить через тот же путь, что и веб-интерфейс.
Добавьте в GITLAB_OMNIBUS_CONFIG:
registry_external_url 'https://registry.git.example.com'
gitlab_rails['registry_enabled'] = true
И проксируйте registry.git.example.com тем же reverse-прокси на внутренний порт (по умолчанию Registry слушает 5050 внутри контейнера — добавьте registry_nginx['listen_port'] = 5050 и проброс порта в compose-файл, либо оставьте GitLab слушать порт напрямую через отдельный ports-маппинг).
После настройки логин и push в реестр работают стандартными Docker-командами:
docker login registry.git.example.com
docker push registry.git.example.com/group/project:latest
Если позже потребуется отдельный, не привязанный к GitLab реестр — например для образов, не связанных с конкретным репозиторием, — есть смысл посмотреть на приватный Docker Registry на VPS как на самостоятельный лёгкий вариант.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
GitLab CE или Gitea — что выбрать для небольшой команды?
Если нужны только git-репозитории и лёгкий CI, Gitea в разы легче по ресурсам (укладывается в 512 МБ — 1 ГБ RAM) и проще в администрировании. GitLab CE оправдан, когда нужны встроенные issue-борды, Merge Request approvals, Container Registry и продвинутый CI/CD с DAG-пайплайнами из коробки. Сравнение подробно разобрано в статье Gitea против GitLab.
Почему GitLab не стартует и в логах видно "Killed"?
Почти всегда это OOM-killer: процессу puma или postgresql не хватило памяти при reconfigure. Проверьте dmesg | grep -i oom на хосте — если видите там gitlab, добавляйте RAM или swap; на 4 ГБ и меньше GitLab CE стабильно не работает.
Можно ли перенести существующий GitLab на другой сервер?
Да, через gitlab-backup create на старом сервере и gitlab-backup restore на новом — при условии, что версии GitLab CE совпадают. Дополнительно нужно вручную перенести /etc/gitlab/gitlab-secrets.json, иначе сломается расшифровка CI/CD-переменных и токенов.
Нужен ли отдельный сервер под CI-раннеры?
Не обязательно для старта, но тяжёлые джобы (сборка образов, интеграционные тесты с базами) начнут конкурировать за ресурсы с самим GitLab, и веб-интерфейс будет подтормаживать во время сборок. Вынос раннера на отдельный VPS снимает эту проблему.
Как обновить GitLab CE без простоя?
Полностью без простоя на self-hosted версии не получится — Omnibus-образ выполняет миграции базы при рестарте, и на это время GitLab недоступен (обычно 1-5 минут). Для критичных инсталляций смотрят в сторону GitLab EE с multi-node конфигурацией.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →