Привет! Это Александр, DevOps инженер команд Страхования в Банки.ру.
Продолжаю серию статей про домашний сервер. В прошлых материалах я рассказал о выборе железа, сборке и настройке NAS и серверов для дома. В этой и последующих статьях опишу установку нужного софта в домашнюю серверную. Для этого вам, возможно, понадобится VPN на виртуальных машинах или на уровне всей домашней сети (у меня второй вариант).

Начать я бы хотел с установки GitLab. На данный момент у меня достаточно ресурсов, чтобы хостить GitLab и другие сервисы, которые использует DevOps-инженер. Но для чего мне нужен GitLab? Тут всё очень просто: в своей работе я использую подход Infrastructure as Code (IaC) — инфраструктура как код. При таком методе конфигурация инфраструктуры описана в файлах в репозитории, который хранит историю изменений.

В итоге из хранилища можно как развернуть нужный софт за считаные минуты, так и вспомнить,  что мы коммитили в репозиторий. GitLab требованиям этого подхода отвечает. К тому же у платформы широкий функционал, который понадобится мне в будущем (CI/CD, например, или хранение terrafrom state в самом GitLab).


Настраиваем DNS

Перед установкой GitLab мне предстояло решить вопрос с DNS и SSL-сертификатом на GitLab и другие мои сервисы. Да, я описывал вариант с Nginx Proxy Manager в прошлых статьях, но при тестировании некоторых сервисов, которые стоят за NPM, выявил ряд проблем и багов. Например, при переходе вроде как по внутренней ссылке в GitLab, домен автоматом менялся на localhost и, соответственно, страницу я так и не получал. Так что, NMP мне не подходил. 

В статье я не буду описывать покупку домена, но подойдёт любой регистратор. Я брал на reg.ru домен в зоне .ru (k8sl.ru) и остался доволен. Фокус заключается в том, чтобы купить любой недорогой домен в личное пользование, изменить NS-записи на NS-сервера Cloudflare и управлять DNS-записями домена через Cloudflare. Мне также пригодится API ключ от Cloudflare для получения SSL-сертификата через плагин cloudflare для certbot. 

Есть несколько вариантов получения сертификата и использования DNS провайдера. Для reg.ru есть плагин к certbot, но у самого reg.ru нет API, через который можно было бы пройти DNS-01 челлендж для получения бесплатного сертификата. Поэтому я и перевожу свои домены к Cloudflare, где мне привычнее ими управлять (надеюсь, что Cloudflare не забанят в России хотя бы к моменту выхода статьи).

Ещё один супербюджетный вариант – использование Duck DNS. Тут тоже всё более менее просто. Сервис выдаст вам личный API token сразу при регистрации, и у вас будет возможность взять до 5 доменов третьего уровня (по типу *.duckdns.org). Вот так, например, выглядит мой аккаунт и домены:

Из плюсов: домены будут бесплатными на постоянной основе. Если же будете покупать домен у регистратора, в первый год заплатите смешные 200-300 рублей, но с продлением на следующие периоды цены будут выше. Из минусов Duck DNS: вы получаете достаточно длинное имя домена, которое  придётся некоторое время вводить руками. 

При первых установках софта по типу GitLab лучше всего использовать SSL-сертификат. В противном случае придётся каждому подключившемуся к нему хосту объяснять, что с этими сервисами всё в порядке. Покупать дорогой Wildcard я не хочу, поэтому буду обходиться сертификатом от Let’s Encrypt, который получу на свой домен с помощью certbot. Бесплатные сертификаты выдаются на 90 дней, но certbot умеет отслеживать сроки выдачи и автоматически обновит сертификат и перезапустить приложение, которое его использует (когда останется меньше 30 дней до истечения срока действия). С такой настройкой мы получаем бесконечный и всегда актуальный SSL-сертификат. 

Так как я нахожусь в домашней сети с внешним серым IP, получать SSL от Let’s Encrypt приходится через челлендж DNS-01. То есть нужен поддерживаемый DNS провайдер (в моем случае Cloudflare), который и предоставляет API- ключ для подтверждения владения данным доменом. 

Работает это следующим образом: 

  • При запуске certbot генерируется txt-запись для вашего домена. 

  • Плагин certbot для Cloudflare создаёт новую временную txt-запись на аккаунте в Cloudflare и в дальнейшем подтверждает, что домен принадлежит именно вам.

Если вы выбрали вариант с  Duck DNS или подобными сервисами, большинство шагов из части с покупкой и сменой DNS нового домена можно пропустить. Вот пример из моего ЛК в reg.ru:

Пример DNS-серверов в панели управления регистратора
Пример DNS-серверов в панели управления регистратора

Быстро пройдусь по шагам: 

  1. Создаем аккаунт у регистратора доменов и покупаем домен.

  2. Создаем аккаунт в Cloudflare и после подтверждения почты и логина в аккаунт жмем кнопку add – Connect domain. Вписываем имя домена и выбираем вариант с ручным добавлением DNS-записей.

  3. Полученные DNS-записи мы вписываем в настройках домена у регистратора.

  4. Ждём некоторое время в панели управления Cloudflare (может быть почти сразу, а бывает придётся подождать час-другой). Как только DNS сервера обновятся, откроется панель управления доменом:

Пример панели управления доменом
Пример панели управления доменом

Здесь нас интересует секция с DNS setup и раздел get your api token в секции с API. Нажимаем на получение токена. Нам нужны вот такие параметры: 

Создание api ключа в Cloudflare
Создание api ключа в Cloudflare

(!) После создания токен будет показан всего один раз, так что лучше записать его сразу.

Подведу промежуточный результат: домен приобретён, DNS записи ведут на Cloudflare, API-токен для управления DNS-записями тоже получен. Переходим к развёртке GitLab.


Установка GitLab на ВМ в домашнем дата-центре

Для начала нам нужно определиться с параметрами ВМ. Почитав документацию и пообщавшись с несколькими ИИ, я принял решение создать под GitLab следующую ВМ:

  • 4 ядра

  • 8 ГБ оперативной памяти

  • 128 ГБ дискового пространства

Этого вполне должно хватить для личного использования. При этом в документации GitLab есть пометка, что лучше не использовать сетевые диски, так как это может сильно замедлить производительность. Так что я создал диск на самом хосте, а потом уже настроил бекапы ВМ на NFS.

Параметры ВМ для GitLab
Параметры ВМ для GitLab

Первоначальная установка GitLab будет идти по вот этой инструкции, но с некоторыми изменениями. Так как мы находимся во внутренней сети, то установка как бы «сломается» где-то под конец, так как встроенный в пакет gitlab-а certbot не сможет получить сертификат. Хорошая новость в том, что это можно вылечить, установив certbot и плагин certbot-а для Cloudflare. 

Быстрый список команд следующий:

sudo apt-get update
sudo apt-get install -y curl openssh-server ca-certificates tzdata perl
sudo apt-get install -y postfix
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.deb.sh | sudo bash
sudo EXTERNAL_URL="https://gitlab.k8sl.ru" apt-get install gitlab-ee #домен меняем на свой

Дождёмся, пока установка GitLab упадёт в ошибку, и продолжим. Теперь мы ставим certbot и плагин Cloudflare к нему:

sudo apt install certbot python3-pip
pip install certbot-dns-cloudflare --break-system-packages

После установки certbot создаём файл cloudflare.ini с токеном, полученным в панели Cloudflare:

echo "dns_cloudflare_api_token = XXXXXXXXXXXXX" > /etc/letsencrypt/cloudflare.ini
chmod 600 /etc/letsencrypt/cloudflare.ini

Для получения сертификата вводим следующую команду:

certbot certonly \
  --dns-cloudflare \
  --agree-tos \
  --email yourmail@example.com \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  --dns-cloudflare-propagation-seconds 60 \
  -d *.k8sl.ru

Вот так выглядит панель управления DNS-записями на момент выдачи SSL-сертификата. Как я писал ранее, добавляется временная txt-запись для подтверждения владения доменом.

Панель управления DNS записями на момент выдачи SSL сертификата
Панель управления DNS записями на момент выдачи SSL сертификата

В итоге в консоли получаем следующий ответ:

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/k8sl.ru/fullchain.pem
Key is saved at:     	/etc/letsencrypt/live/k8sl.ru/privkey.pem
This certificate expires on 2025-06-29.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

Итак, wildcard-сертификат у нас есть, теперь подправим конфиг gitlab. 

Редактируем файл /etc/gitlab/gitlab.rb и добавляем следующие строки (или находим и меняем путь к сертификатам, которые мы выписали, в соответствующих строках):

nginx['ssl_certificate'] = "/etc/letsencrypt/live/k8sl.ru/fullchain.pem"
nginx['ssl_certificate_key'] = "/etc/letsencrypt/live/k8sl.ru/privkey.pem"

После этого сохраняем файл и вводим в консоль команду:

sudo gitlab-ctl reconfigure

На этом этапе все должно пройти успешно. При посещении gitlab.k8sl.ru мы видим вот такую картину:

И рабочий SSL сертификат:

На этом базовая установка GitLab закончена. Забираем в консоли первичный пароль на root пользователя:

cat /etc/gitlab/initial_root_password | grep Password:

Через этот пароль мы и попадём в аккаунт админа Gitlab. Не забывайте сменить пароль, так как временный активен в течение 48 часов!

Также из документации certbot я узнал, что при обновлении сертификата можно автоматически перезапускать сервис, который этот сертификат использует. Поэтому используем следующие команды для создания скрипта перезагрузки встроенного в GitLab nginx сервера:

sudo sh -c 'printf "#!/bin/sh\nsudo gitlab-ctl restart nginx\n" > /etc/letsencrypt/renewal-hooks/post/gitlab.sh'
sudo chmod 755 /etc/letsencrypt/renewal-hooks/post/gitlab.sh

Дополнительно ещё хотелось бы описать настройку отправки почты с сервера GitLab. Это понадобится для удобного обновления/сброса пароля, получения уведомлений и возможности регистрации новых пользователей через письмо-приглашение. 

Изначально я планировал создать почту на ранее полученном мною домене k8sl.ru, но когда увидел ценники на внешний хостинг почты в российских и зарубежных сервисах с тарификацией поштучно за почтовый ящик, то решил создать новый бесплатный ящик на mail.ru и использовать его. Единственный момент, на котором я заострю внимание: у mail.ru нужно создавать пароль для внешних приложений. Тут описано, как это сделать. 

Я же просто предоставлю вам пример конфига для GitLab, который в ubuntu находится по пути /etc/gitlab/gitlab.rb (эти настройки подойдут и в другие ваши сервисы, где нужно только отправлять письма):

gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.mail.ru"
gitlab_rails['smtp_port'] = 465
gitlab_rails['smtp_user_name'] = "delta-prj@mail.ru" #вот тут ваш адрес почты
gitlab_rails['smtp_password'] = "введите ваш внешний пароль"
gitlab_rails['smtp_domain'] = "mail.ru"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = false
gitlab_rails['smtp_tls'] = true
gitlab_rails['smtp_pool'] = false


gitlab_rails['smtp_openssl_verify_mode'] = 'peer'

gitlab_rails['gitlab_email_enabled'] = true

gitlab_rails['gitlab_email_from'] = 'delta-prj@mail.ru' #без этих настроек сервер mail.ru не давал отправлять почту, поэтому указываем откуда и от кого приходят письма
gitlab_rails['gitlab_email_display_name'] = 'gitlab.k8sl.ru'
gitlab_rails['gitlab_email_reply_to'] = 'alex.shcherbakov@inbox.ru' #указал куда отвечать на всякий случай

В заключении статьи подытожу, что у меня получилось:

  • Приобрести домен, на который можно сделать неограниченное число субдоменов. 

  • Перенаправить его на NS сервера Cloudflare, что в итоге позволяет мне использовать API Cloudflare. 

  • Описать получение SSL-сертификатов от Let’s Encrypt во внутренней сети с использованием API Cloudflare.

  • Установить и настроить GitLab с «вечным» SSL сертификатом и отправкой почты через внешний почтовый сервис. 

По факту, инструкция в статье универсальна, так как по ней можно настроить SSL и почту для большинства сервисов внутри домашней сети.

Для подготовки этой статьи были использованы следующие ресурсы:

Комментарии (20)


  1. project_delta Автор
    14.05.2025 07:04

    Для дочитавших статью до конца - бонусная ссылка: https://github.com/Lakr233/GitLab-License-Generator
    Вы явно знаете, что с этим делать)


  1. kenomimi
    14.05.2025 07:04

    Никогда не выставляйте крупные веб-морды наружу. Они все дырявы насквозь, и взлом - вопрос времени. Причем хорошо, если взломает хулиган, а не распространитель политоты или цопе... Науржу только ssh + ipsec (или другой впн), остальное внутри. В крайнем случае накрывайте реверс-прокси с клиентским сертом.

    Для почты есть отличное решение mailcow, набор преднастроеных контейнеров. Если донастроить по инструкции - почта ходит прекрасно, ничего не режет. Пользуюсь довольно давно.

    Для "бытового" DNS/DHCP лучше всего TechnitiumDNS - при полной бесплатности там шикарная веб-морда для конфигурации. И тоже распространяется как контейнер в том числе.


  1. dsoastro
    14.05.2025 07:04

    а как вы направляете трафик с cloudflare в домашнюю серверную с серым ИП адресом?


    1. project_delta Автор
      14.05.2025 07:04

      А тут всё просто - я ставлю А запись на ip в домашней сети. ну к примеру 192.168.1.10 . И оно работает, но правда только внутри домашней сети. Можно заморочиться и выкинуть наружу, но я не хочу.


      1. dsoastro
        14.05.2025 07:04

        тогда зачем морочиться с сертами? можно обычный http использовать. ну и платное доменное имя для домашней сети? можно поднять внутри домашней сети свой днс сервер и сделать зону local например


        1. project_delta Автор
          14.05.2025 07:04

          ну я писал и про Duckdns, он бесплатный и работает в локальной сети.
          Сертификаты нужны, чтобы на runner и в docker не получать ошибку x509 при запросе на этот гитлаб. В общем, я тут просто описал свою методику.


        1. Anthony_S_Chet
          14.05.2025 07:04

          А можно сгенерировать локальный сертификат хоть на 20 лет и забыть об обновлении. Только распихать CA-шку по клиентским компам.


          1. project_delta Автор
            14.05.2025 07:04

            вот поэтому я и беру летсэнкрипт, потому что его CA уже раскидан на все компы) А так, сертбот сам потом обновляет и рестартит сервис, так что в целом это "бессрочный" сертификат)


          1. kenomimi
            14.05.2025 07:04

            Конечные серты, сгенеренные более чем на 3 месяца очень скоро будут недействительны везде. Вопрос времени, когда это правило заедет в openssl. На макоси уже сейчас конечные серты сроком более чем на год не работают - яблоко первым начало применять эту инициативу...

            А еще сейчас каждая контора разработчиков считает за хороший тон не использовать системные серты, а класть их максимально неудобно и нестандартно. Гитлаб раннер в одном месте СА хранит, докер в другом, жава в третьем, ... попробуй собери их всех! А еще прокинь кастомные CA в контейнеры, которых мешок и они все разные... Испытай настоящие боль и унижение.

            Так что LE сейчас очень выручает, если домены на реальном tld, а не на своем типа *.lan


  1. buldo
    14.05.2025 07:04

    Это странно, что у вас были проблемы с gitlab на NPM. Может недонастроили какие-то доп параметры в gitlab? Помню, что делал такое при установке за NPM.


    1. project_delta Автор
      14.05.2025 07:04

      у меня проблема была на вкладке с раннерами при работе через npm. То есть нажимаешь создать раннера, а в итоге получаешь ошибку и редирект на локалхост. Поэтому в итоге решил делать через certbot. По факту пока что гитлаб единственный сервис, который работает таким способом, остальные сервисы закинуты в кубер и там уже certmanager автоматически запрашивает сертификат при деплое ingress-а


      1. buldo
        14.05.2025 07:04

        Хм. Странно. У меня такой ошибки не было. Ну или другая версия, или что-то с конфигами


        1. project_delta Автор
          14.05.2025 07:04

          вполне возможно) ну и я пока что ушёл от npm, так как обкатал новые способы получения сертификатов


  1. KarakoiD
    14.05.2025 07:04

    У меня в локальной сети работает сервер гитлаб и на другой машине раннер запущен, зарегался без проблем по локальному адресу без серта, джобы идут, все норм. Странно


  1. kurandx
    14.05.2025 07:04

    Скажу вам по секрету, у regru есть api, и даже можно dns челлендж пройти(у меня уже как года два работает все, никаких проблем не было)


  1. karabanov
    14.05.2025 07:04

    Тоже самое только в контейнере

    docker-compose.yml
    networks:
      gitlab_net:
        name: gitlab
        driver: bridge
        ipam:
          driver: default
          config:
          - subnet: '172.28.3.0/24'
            gateway: '172.28.3.254'
            ip_range: '172.28.3.0/28'
        driver_opts:
          com.docker.network.bridge.name: gitlab-bridge
    
    services:
      gitlab:
        container_name: gitlab
        hostname: gitlab.example.com
        image: gitlab/gitlab-ee:17.11.2-ee.0
        shm_size: '512mb'
        environment:
          SENSELESS: variable
          GITLAB_OMNIBUS_CONFIG: |
              ### Postgresql settings
              ###! Docs: https://docs.gitlab.com/omnibus/settings/database.html
              postgresql['enable'] = true
              postgresql['listen_address'] = '0.0.0.0'
              postgresql['port'] = 5432
              postgresql['md5_auth_cidr_addresses'] = ['127.0.0.1/24', '172.28.3.0/24', '197.15.22.0/24']
              postgresql['trust_auth_cidr_addresses'] = %w(127.0.0.1/24)
    
              # force ssl on all connections defined in trust_auth_cidr_addresses and md5_auth_cidr_addresses
              postgresql['hostssl'] = true
              postgresql['db_sslmode'] = 'require'
    
              # Disable monitoring
              prometheus_monitoring['enable'] = false
              prometheus['enable'] = false
              postgres_exporter['enable'] = false
              redis_exporter['enable'] = false
              node_exporter['enable'] = false
              alertmanager['enable'] = false
    
              # Custom settings
              postgresql['max_connections'] = 512
              postgresql['shared_buffers'] = '4096MB'
    
              external_url 'https://gitlab.example.com'
    
              # gitlab_rails['gitlab_ssh_host'] = 'ssh.host_example.com'
              gitlab_rails['time_zone'] = 'Europe/Moscow'
              gitlab_rails['gitlab_email_enabled'] = true
              gitlab_rails['gitlab_email_display_name'] = 'My GitLab'
              gitlab_rails['webhook_timeout'] = 40
    
              ### GitLab email server settings
              ###! Docs: https://docs.gitlab.com/omnibus/settings/smtp.html
              ###! **Use smtp instead of sendmail/postfix.**
    
              gitlab_rails['smtp_enable'] = true
              gitlab_rails['smtp_address'] = "smtp.yandex.ru"
              gitlab_rails['smtp_port'] = 465
              gitlab_rails['smtp_user_name'] = "gitlab@example.com"
              # user password
              #gitlab_rails['smtp_password'] = "xxxxxxxxxxxxxxxxxxx"
              # application password
              gitlab_rails['smtp_password'] = "xxxxxxxxxxxxxxxxxxx"
              gitlab_rails['smtp_domain'] = "example.com"
              gitlab_rails['gitlab_email_from'] = "gitlab@example.com"
              gitlab_rails['smtp_authentication'] = "login"
              gitlab_rails['smtp_enable_starttls_auto'] = false
              gitlab_rails['smtp_tls'] = true
              gitlab_rails['smtp_openssl_verify_mode'] = "peer"
    
              letsencrypt['enable'] = true
              letsencrypt['contact_emails'] = ['admin@example.com']
              letsencrypt['auto_renew_hour'] = "12"
              letsencrypt['auto_renew_minute'] = "30"
              letsencrypt['auto_renew_day_of_month'] = "*/7"
    
              nginx['redirect_http_to_https'] = true
              nginx['hsts_max_age'] = 63072000
              nginx['hsts_include_subdomains'] = true
              nginx['http2_enabled'] = true
              nginx['proxy_set_headers'] = {
                'X-Forwarded-Proto' => 'https',
                'X-Forwarded-Ssl' => 'on'
              }
    
              registry['enable'] = false
    
              ### Backup Settings
              ###! Docs: https://docs.gitlab.com/omnibus/settings/backups.html
              gitlab_rails['manage_backup_path'] = true
              gitlab_rails['backup_path'] = "/var/opt/gitlab/backups"
              ## Limit backup lifetime to 2 days - 172800 seconds
              gitlab_rails['backup_keep_time'] = 86400
    
        # In the event of an disaster,
        # the container must be stopped manually
        # and must not start automatically.
        restart: unless-stopped
        ports:
          - "197.15.22.27:22:22"
          - "80:80"
          - "443:443"
        volumes:
          - './gitlab.example.com/config:/etc/gitlab'
          - './gitlab.example.com/logs:/var/log/gitlab'
          - './gitlab.example.com/data:/var/opt/gitlab'
          - './gitlab.example.com/hooks:/opt/gitlab/embedded/service/gitlab-shell/hooks'
          - './gitlab.example.com/backups:/var/opt/gitlab/backups'
        networks:
          gitlab_net:
            ipv4_address: 172.28.3.1

    DNS-01 challenge в GitLab не реализован, поэтому придётся использовать HTTP-01 challenge, либо получать сертификат с помощью certbot


  1. aliakseika
    14.05.2025 07:04

    А почему gitlab, а не gitea или firgejo? Ресурсов в разы меньше нужно и функционал тот же


    1. project_delta Автор
      14.05.2025 07:04

      гитлаб распространён как ci/cd тулза в компаниях. Про gitea ни разу не видел в вакансиях)


      1. aliakseika
        14.05.2025 07:04

        Ну в gitea используется GitHub actions для cicd). Если по ставится не для изучения чисто cicd gitlab, то в чем ещё его преимущество?) Я сам поставил на домашнем сервере firgejo (форк gitea) и выбирал между ним и gitlab. Вот пытаюсь понять в чем причина популярности gitlab


        1. aliakseika
          14.05.2025 07:04

          Forgejo, автозамена сработала)