Независимый Git и сборочные пайплайны без SaaS-закладок: Gitea и GitLab, раннеры GitLab Runner / Jenkins / Drone (Woodpecker) и приватные реестры Docker/Harbor Print

  • github, Gitea, Jenkins, Harbor
  • 0

Когда исходники, сборки и артефакты живут у вас, исчезают странные ограничения по сетям и ценам «за трафик наружу», а безопасность перестаёт быть переговорами с чужой политикой. В «independent cloud» это ощущается особенно ярко: лёгкие VPS для Git и CI в нужных регионах, честные vCPU и NVMe без оверселла, предсказуемая сеть. Ниже — практичный путь: поднять Gitea или GitLab, подключить раннеры под разные сценарии, завести приватный реестр (минималистичный Docker Registry или тяжеловесный, но удобный Harbor), связать всё в устойчивый контур и не переплатить ни за что лишнее. Покажу конкретные конфиги на Ubuntu/Debian, без «магии» и с реальными доменами и портами. Примерный периметр: исходники на git.example.com, тяжёлые проекты при этом сидят на gitlab.example.com, лёгкий контейнерный CI в ci.example.com, реестр образов в registry.example.com, а для продвинутых политик артефактов — harbor.example.com. Сервер с публичным адресом 203.0.113.10, DNS настроены на A/AAAA-записи, сертификаты выпустим локально.

Сначала приведём систему в порядок и дадим сервисам воздух. Обновляем пакеты, включаем UFW, открываем только нужные порты и фиксируем тайм в NTP, иначе вебхуки и кеши будут жить в параллельной вселенной. На одном узле для начала хватит 4 vCPU и 8–16 ГБ RAM; если проектов много, выносите GitLab на отдельную машину — он любит память и диски.

sudo apt update && sudo apt -y full-upgrade
sudo apt -y install curl gnupg2 ca-certificates lsb-release jq ufw ntp
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable

Дальше выбираем Git-сервер под характер команды. Gitea — быстрый и экономичный вариант, идеален как «независимый Git с легкими issue и PR», тянет десятки команд на крошечных инстансах. GitLab — тяжелее, зато «всё-в-одном» с богатым UI и нативными раннерами. Нередко их комбинируют: Gitea как простой и быстрый «источник правды» и Drone/Woodpecker как сверхлёгкий CI, а GitLab держат только там, где его UX действительно нужен.

Для Gitea удобно стартовать в Docker Compose с PostgreSQL и обратным прокси на Nginx. Так проще обновлять и переносить. Ниже — полностью рабочий стек, где Gitea живёт на git.example.com, база — в отдельном контейнере, трафик шифруется Nginx’ом, а резервные копии лежат на примонтированном каталоге. Версии используются конкретные, без «latest», чтобы обновления были осознанными.

# /opt/gitea/docker-compose.yml
services:
  postgres:
    image: postgres:15.6
    environment:
      POSTGRES_DB: gitea
      POSTGRES_USER: gitea
      POSTGRES_PASSWORD: gitea_S3cretP@ss
    volumes:
      - /opt/gitea/pgdata:/var/lib/postgresql/data
    restart: unless-stopped

  gitea:
    image: gitea/gitea:1.21.11-rootless
    environment:
      GITEA__database__DB_TYPE: postgres
      GITEA__database__HOST: postgres:5432
      GITEA__database__NAME: gitea
      GITEA__database__USER: gitea
      GITEA__database__PASSWD: gitea_S3cretP@ss
      GITEA__server__DOMAIN: git.example.com
      GITEA__server__ROOT_URL: https://git.example.com/
      GITEA__server__SSH_DOMAIN: git.example.com
      GITEA__server__SSH_PORT: 2222
      GITEA__security__INSTALL_LOCK: true
      GITEA__service__DISABLE_REGISTRATION: true
    volumes:
      - /opt/gitea/data:/var/lib/gitea
      - /etc/timezone:/etc/timezone:ro
    depends_on:
      - postgres
    expose:
      - "3000"
      - "2222"
    restart: unless-stopped

  nginx:
    image: nginx:1.25.5
    volumes:
      - /opt/gitea/nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro
    ports:
      - "80:80"
      - "443:443"
      - "2222:2222"
    depends_on:
      - gitea
    restart: unless-stopped

Конфигурация Nginx завершает TLS и аккуратно проксирует UI и SSH-порт Gitea (rootless-сборка слушает 2222 — это нормально). QUIC включать не обязательно, но приятно для веб-интерфейса.

# /opt/gitea/nginx.conf
server {
    listen 80;
    server_name git.example.com;
    return 301 https://git.example.com$request_uri;
}

server {
    listen 443 ssl http2;
    server_name git.example.com;

    ssl_certificate     /etc/letsencrypt/live/git.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/git.example.com/privkey.pem;
    add_header Strict-Transport-Security "max-age=31536000" always;

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_pass http://gitea:3000;
    }

    # SSH over TCP 2222 пробрасываем напрямую
    location /ssh {
        proxy_pass http://gitea:2222;
    }
}

# Проксирование сырого TCP для SSH (stream)
stream {
    upstream gitea_ssh {
        server gitea:2222;
    }
    server {
        listen 2222;
        proxy_pass gitea_ssh;
    }
}

После выпуска сертификата стандартным образом через certbot certonly --standalone -d git.example.com стек стартует одной командой: docker compose up -d. При первом заходе под админом сразу выключайте само-регистрацию и подключайте SSO/LDAP, если он есть. В эту же Gitea очень органично встаёт Drone или Woodpecker как контейнерный CI: аутентификация происходит через OAuth Gitea, а каждый билд — это изолированный контейнер со своим набором секретов.

Для минималистичного CI берём Drone (или его максимально совместный форк Woodpecker). Сервер и раннер поднимаются в паре; сервер слушает вебхуки Gitea, раннер забирает задания из очереди. Ниже — конфигурация из двух контейнеров, где значения уже проставлены, а домен ci.example.com обслуживается через Nginx этого же узла или отдельного.

# /opt/drone/docker-compose.yml
services:
  drone-server:
    image: drone/drone:2.22.0
    environment:
      DRONE_GITEA_SERVER: https://git.example.com
      DRONE_GITEA_CLIENT_ID: 7f3a7c1e0a2b4c99
      DRONE_GITEA_CLIENT_SECRET: 4f1b9a6c7d8e0f21f4d2e5a7b6c9a1d2
      DRONE_RPC_SECRET: 1f8a6c2e4b7d9a0c3e6f1a2b5c7d9e0f
      DRONE_SERVER_HOST: ci.example.com
      DRONE_SERVER_PROTO: https
      DRONE_USER_CREATE: username:dev,admin:true
    volumes:
      - /opt/drone/data:/data
    ports:
      - "8080:80"
    restart: unless-stopped

  drone-runner:
    image: drone/drone-runner-docker:1.8.3
    environment:
      DRONE_RPC_PROTO: http
      DRONE_RPC_HOST: drone-server
      DRONE_RPC_SECRET: 1f8a6c2e4b7d9a0c3e6f1a2b5c7d9e0f
      DRONE_RUNNER_CAPACITY: 2
      DRONE_RUNNER_NAME: runner-01
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    depends_on:
      - drone-server
    restart: unless-stopped

Пайплайн описывается в файле .drone.yml в корне репозитория. Ниже — реальная сборка контейнера с BuildKit и публикацией в приватный реестр с кешированием слоёв. Логин происходит через секреты, заданные в UI Drone.

kind: pipeline
type: docker
name: build_and_push

steps:
  - name: build
    image: docker:26.1.3
    environment:
      DOCKER_BUILDKIT: 1
    commands:
      - echo $REG_PASS | docker login registry.example.com -u $REG_USER --password-stdin
      - docker buildx create --use --name builder0
      - docker buildx build --push --tag registry.example.com/acme/app:${DRONE_COMMIT_SHA} --cache-to type=registry,ref=registry.example.com/acme/app:cache,mode=max --cache-from type=registry,ref=registry.example.com/acme/app:cache .
    secrets: [ REG_USER, REG_PASS ]

Если команде нужен более привычный «оркестр» со сложными пайплайнами, подключаем Jenkins. Его удобно держать как контейнер с внешним volume под JENKINS_HOME, а агенты поднимать под каждую сборку как ephemeral-контейнеры на этом же хосте. В Jenkinsfile ниже используется Docker агент, BuildKit и публикация в реестр; на практике такие пайплайны безболезненно расходятся на десятки проектов.

// Jenkinsfile в репозитории
pipeline {
  agent { docker { image 'docker:26.1.3' args '-v /var/run/docker.sock:/var/run/docker.sock' } }
  environment {
    DOCKER_BUILDKIT = '1'
    REGISTRY = 'registry.example.com'
    IMAGE = 'acme/app'
  }
  stages {
    stage('Login') {
      steps {
        sh 'echo $REG_PASS | docker login $REGISTRY -u $REG_USER --password-stdin'
      }
    }
    stage('Build & Push') {
      steps {
        sh '''
          docker buildx create --use --name builder0 || true
          docker buildx build \
            --tag $REGISTRY/$IMAGE:${GIT_COMMIT} \
            --cache-to type=registry,ref=$REGISTRY/$IMAGE:cache,mode=max \
            --cache-from type=registry,ref=$REGISTRY/$IMAGE:cache \
            --push .
        '''
      }
    }
  }
  options { timestamps() }
}

GitLab хорош там, где «надо всё и сразу». Установим Omnibus CE на отдельную машину gitlab.example.com и дадим ему RAM и диск. Суть установки проста: репозиторий, пакет, внешняя ссылка, затем gitlab-ctl reconfigure. Если включаете встроенный контейнерный реестр GitLab, убедитесь, что диск для registry быстрый, иначе сборки начнут ждать I/O.

# GitLab CE на Ubuntu/Debian
curl -fsSL https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash
sudo EXTERNAL_URL="https://gitlab.example.com" apt -y install gitlab-ce
sudo gitlab-ctl reconfigure

Раннер GitLab ставится отдельно и регистрируется командой с токеном проекта или группы. Docker-executor остаётся самым предсказуемым: он изолирует зависимости билдов и не «загрязняет» хост. Пример ниже действительно регистрирует раннер под Docker на этом же узле.

sudo apt -y install gitlab-runner
sudo gitlab-runner register \
  --non-interactive \
  --url "https://gitlab.example.com" \
  --registration-token "GR1348941aBcDe" \
  --executor "docker" \
  --docker-image "docker:26.1.3" \
  --description "runner-docker-01" \
  --tag-list "docker,ubuntu" \
  --run-untagged="true" \
  --locked="false"
sudo systemctl enable --now gitlab-runner

Файл .gitlab-ci.yml ниже собирает образ и пушит его в приватный реестр, пользуясь BuildKit и кешами слоёв. Переменные REG_USER/REG_PASS задаются в настройках проекта.

stages: [ build ]
variables:
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: ""
  DOCKER_BUILDKIT: "1"

build-and-push:
  stage: build
  image: docker:26.1.3
  services: [ docker:26.1.3-dind ]
  script:
    - echo "$REG_PASS" | docker login registry.example.com -u "$REG_USER" --password-stdin
    - docker buildx create --use --name builder0 || true
    - docker buildx build --push --tag registry.example.com/acme/app:$CI_COMMIT_SHA --cache-to type=registry,ref=registry.example.com/acme/app:cache,mode=max --cache-from type=registry,ref=registry.example.com/acme/app:cache .

Теперь к приватным реестрам. Есть два устойчивых пути. Минимальный — официальный registry:2, который прекрасен в своей простоте и удобен как реплика/кеш. Промышленный — Harbor: с UI, проектами, политиками, ретеншном, Trivy-сканером, робот-аккаунтами и репликациями между площадками. В повседневной жизни это выглядит так: на небольших проектах достаточно registry:2 за Nginx’ом с TLS и базовой аутентификацией; как только появляются разные команды и правила хранения артефактов — включается Harbor.

Для «маленького, но честного» реестра ставим registry:2 и Nginx с htpasswd. Пользователь dev и пароль задаются один раз, политики pull/push регулируются уровнями доступа в CI.

sudo apt -y install apache2-utils docker.io docker-compose-plugin
sudo mkdir -p /opt/registry/{data,auth}
htpasswd -bBc /opt/registry/auth/htpasswd dev S3cretDevPass

Compose-файл сводит вместе реестр и обратный прокси; TLS лежит в /etc/letsencrypt.

# /opt/registry/docker-compose.yml
services:
  registry:
    image: registry:2.8.3
    environment:
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /var/lib/registry
    volumes:
      - /opt/registry/data:/var/lib/registry
    expose:
      - "5000"
    restart: unless-stopped

  nginx:
    image: nginx:1.25.5
    volumes:
      - /opt/registry/nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - /opt/registry/auth:/etc/nginx/auth:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - registry
    restart: unless-stopped

Конфигурация Nginx проверяет логин/пароль, шифрует трафик и проксирует запросы к registry.

# /opt/registry/nginx.conf
server {
    listen 80;
    server_name registry.example.com;
    return 301 https://registry.example.com$request_uri;
}
server {
    listen 443 ssl http2;
    server_name registry.example.com;

    ssl_certificate     /etc/letsencrypt/live/registry.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/registry.example.com/privkey.pem;

    location /v2/ {
        auth_basic           "Private Registry";
        auth_basic_user_file /etc/nginx/auth/htpasswd;

        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_pass http://registry:5000;
        client_max_body_size 2G;
    }
}

С Harbor подход иной: это набор сервисов (Core, Registry, Notary, Trivy, Portal), который ставится скриптом и docker-compose под системным пользователем. Минимальный конфиг указывает домен, включённый HTTPS и пароли администратора. Harbor удобен тем, что в один клик даёт проекты, роли, робот-аккаунты и репликации между регионами, а ещё умеет ретеншн «держать последние N образов» и вычищать висячие манифесты без боли.

# /opt/harbor/harbor.yml (фрагмент минимальной конфигурации)
hostname: harbor.example.com
https:
  port: 443
  certificate: /etc/letsencrypt/live/harbor.example.com/fullchain.pem
  private_key: /etc/letsencrypt/live/harbor.example.com/privkey.pem
harbor_admin_password: "Adm1nHarborP@ss"
database:
  password: "HarborDB_P@ss"
data_volume: /data
trivy:
  enabled: true

После генерации harbor.yml установка запускается скриптом install.sh из дистрибутива Harbor; служебные контейнеры поднимутся автоматически, а UI станет доступен по https://harbor.example.com. На практике Harbor лучше держать на отдельном диске с быстрым NVMe — garbage collector в таком случае выполняется быстро, а репликации не страдают от узкого места I/O.

Секреты и доступы должны быть предсказуемыми. В Gitea включайте «подписанные вебхуки» и проверяйте их на стороне CI, в Drone/Woodpecker храните секреты на уровне репозитория или организации и используйте переменные окружения в шагах, в Jenkins пользуйтесь Credentials Store и консервативными правами на Job’ы, в GitLab держите переменные в «Protected» и связывайте их с защищёнными ветками. Логины в реестр делайте через docker login с токенами, а не с паролями от личных аккаунтов. Если есть возможность, переходите на подписанные образы через cosign и включайте проверки в деплоях — это почти бесплатная страховка от подмены.

Теперь немного про производительность и деньги. Самое дешёвое улучшение — BuildKit с кешем слоёв в реестре: шаги сборки перестают заново скачивать полмира, а образы выкатываются в считанные минуты. Второе — перенос реестра ближе к раннерам: каждый мегабайт скачивается локально, а не через границы регионов. Третье — раздельные узлы: Git ближе к людям (низкая латентность UI), раннеры ближе к реестру (низкая латентность pull/push). На практике это даёт двузначное сокращение времени пайплайна без доплаты за «магические ускорители». Ещё одна важная деталь — метрики: держите «health-страницы» на каждом сервисе и собирайте базовые показания в Prometheus/Grafana; если это кажется избыточным, начинайте с логов Nginx и docker stats — уже они дают понятную картину, где именно «тесно».

Сценарий миграции «в два шага». Сначала поднимаете Gitea/реестр и прокладываете к ним тестовый репозиторий и pipeline, убедившись, что пуши и сборка образа действительно живут на ваших дисках. Затем включаете Jenkins или Drone для реальных проектов и подмешиваете GitLab там, где он нужен. Раннеры можно размножать горизонтально, оставляя одинаковые конфиги и разделяя нагрузку через теги или очереди. Когда инфраструктура начинает «дышать» под привычным грузом, добавляете второй реестр в другом регионе и настраиваете репликацию: образы автоматически копируются ближе к «второму» рынку, пользователи перестают ждать межрегиональные сети.

Если хочется начать без долгих подготовок, удобно арендовать пару VPS с NVMe в близких к аудитории локациях и развернуть всё по конфигам из этого текста. Мы помогаем с установкой и преднастройкой: Git-сервер, выбранный раннер, реестр, сертификаты и базовая безопасность на UFW, а также берём на себя бесплатную миграцию репозиториев и CI-пайплайнов. Проверить доступность нужных регионов можно заранее и выбрать географию под ваши команды; дальше остаётся только нажать «push» и наблюдать, как сборки начинают вести себя предсказуемо.


Was this answer helpful?

Related Articles

Какие есть боты/сервисы, которые стоит добавить в исключения? Практический гайд для защиты сайта и бизнеса В современных условиях кибербезопасности настройка блокировок и фильтров — обязательная мера для... Что делать, если сертификаты Let’s Encrypt не обновляются? Простое решение за 5 минут Сертификаты от Let’s Encrypt стали стандартом для бесплатной автоматической защиты сайтов по... Какие сервисы и решения реально помогают? Топ-10 инструментов Почему взламывают сайты и что самое опасное? Современный сайт на WordPress, Битрикс, Joomla,... Лучшие версии PHP и MySQL сейчас для WordPress: что выбрать для максимальной стабильности и скорости? WordPress — самая популярная CMS в мире, и именно поэтому вопрос о правильной версии PHP и... Где сейчас захостить видео, чтобы его просто вставлять на свой сайт без рекламы? Лучшие альтернативы YouTube В 2025 году все чаще сталкиваемся с ситуацией: YouTube работает с перебоями, вставки грузятся...
« Back

Powered by WHMCompleteSolution


Knowledgebase