MAATRIX / Блог / Drone CI в Docker Compose: готовый файл

Drone CI в Docker Compose: готовый файл

MAATRIX

Jenkins требует отдельный сервер под агенты и постоянно тянет за собой плагины с конфликтующими версиями Java. GitLab CI удобен, но заставляет переезжать на GitLab целиком. Если репозиторий уже живёт на GitHub или в самостоятельно поднятой Gitea, а нужен просто рабочий CI/CD без лишней инфраструктуры — Drone CI решает это за один docker-compose up. Ниже — рабочий файл конфигурации и пошаговая настройка от установки до первого зелёного пайплайна.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Что такое Drone CI и почему он контейнерный по своей природе

Drone CI построен вокруг одной идеи: каждый шаг пайплайна — это отдельный Docker-контейнер. Не процесс на общем агенте, не shell-скрипт в чужом окружении, а изолированный запуск конкретного образа с конкретной задачей. Шаг test выполняется в контейнере golang:1.22, шаг build — в node:20-alpine, шаг deploy — в вашем собственном образе с ansible или kubectl. Никакого расползания зависимостей между шагами и никакого "у меня на агенте работало".

Архитектурно Drone состоит из двух компонентов:

  • drone-server — веб-интерфейс, база данных, OAuth-авторизация через Git-провайдера, приём вебхуков и постановка задач в очередь;
  • drone-runner-docker — воркер, который забирает задачи из очереди и запускает их как Docker-контейнеры на своей машине.

Оба компонента разворачиваются как обычные сервисы в Docker Compose и легко масштабируются: нужно больше параллельных сборок — добавьте ещё один runner на соседнем сервере с тем же RPC-секретом. Runner сам стучится к серверу по gRPC, никакой отдельной службы вроде Jenkins-агента через SSH не требуется.

По сравнению с GitLab CI на VPS Drone не тянет за собой весь GitLab: если у вас уже есть репозиторий на GitHub, GitLab.com, Gitea или Gogs, Drone подключается к нему через OAuth и ничего не дублирует. Это в буквальном смысле CI-сервер и ничего больше — плюс для тех, кому не нужен встроенный issue-tracker и wiki, а нужен именно конвейер сборки и деплоя.

Готовый docker-compose.yml: сервер и runner

Перед запуском сгенерируйте общий секрет, которым сервер и runner будут друг другу доверять:

openssl rand -hex 16

Сохраните значение — оно пойдёт в DRONE_RPC_SECRET обоих сервисов. Дальше — сам файл:

# docker-compose.yml
version: "3.8"

services:
  drone-server:
    image: drone/drone:2
    container_name: drone-server
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - drone-data:/data
    environment:
      - DRONE_GITEA_SERVER=https://git.example.com
      - DRONE_GITEA_CLIENT_ID=your_client_id
      - DRONE_GITEA_CLIENT_SECRET=your_client_secret
      - DRONE_RPC_SECRET=ваш_секрет_из_openssl
      - DRONE_SERVER_HOST=drone.example.com
      - DRONE_SERVER_PROTO=https
      - DRONE_USER_CREATE=username:ваш_логин,admin:true
      - DRONE_LOGS_DEBUG=false

  drone-runner:
    image: drone/drone-runner-docker:1
    container_name: drone-runner
    restart: unless-stopped
    depends_on:
      - drone-server
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - DRONE_RPC_HOST=drone-server
      - DRONE_RPC_PROTO=http
      - DRONE_RPC_SECRET=ваш_секрет_из_openssl
      - DRONE_RUNNER_CAPACITY=2
      - DRONE_RUNNER_NAME=runner-1

volumes:
  drone-data:

Два момента здесь важны. Во-первых, drone-server слушает только 127.0.0.1:8080 — наружу его отдаёт reverse-proxy с HTTPS, напрямую в интернет держать CI-сервер незачем. Во-вторых, runner получает доступ к /var/run/docker.sock: контейнеры пайплайнов запускаются рядом с самим runner-контейнером, а не внутри него (docker-out-of-docker). Отсюда практическое ограничение: DRONE_RUNNER_CAPACITY реально упирается в ресурсы хоста, а не в какую-то внутреннюю квоту раннера.

Секцию DRONE_GITEA_* замените на нужный провайдер: DRONE_GITHUB_CLIENT_ID/DRONE_GITHUB_CLIENT_SECRET для GitHub, DRONE_GITLAB_* для GitLab. Про сам OAuth — в следующем разделе.

Поднимаем:

docker compose up -d
docker compose logs -f drone-server

Если в логах runner видно successfully pinged the remote server — связка сервер/runner работает.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Подключение GitHub или Gitea через OAuth

Drone не хранит собственных пользователей — авторизация полностью делегирована Git-провайдеру. Для GitHub заведите OAuth App в Settings → Developer settings → OAuth Apps:

  • Homepage URL: https://drone.example.com
  • Authorization callback URL: https://drone.example.com/login

Полученные Client ID и Client Secret идут в переменные DRONE_GITHUB_CLIENT_ID и DRONE_GITHUB_CLIENT_SECRET.

Для самостоятельно размещённой Gitea процесс тот же, но раздел называется Settings → Applications → OAuth2 Applications, а callback URL — https://drone.example.com/login. Если Gitea крутится в том же docker-compose-стеке или на соседнем сервере, укажите её внутренний адрес в DRONE_GITEA_SERVER — трафик авторизации пойдёт напрямую, без выхода в интернет.

После рестарта сервера с новыми переменными зайдите на https://drone.example.com, авторизуйтесь через провайдера — Drone запросит доступ к репозиториям и вебхукам. Затем в интерфейсе Drone откройте нужный репозиторий и нажмите Activate: сервис сам создаст вебхук в настройках репозитория, который будет присылать события push и pull request.

Первый вход создаёт вас как обычного пользователя, а не администратора — поэтому в docker-compose.yml выше стоит DRONE_USER_CREATE=username:ваш_логин,admin:true. Строка при старте сервера один раз создаёт учётку-администратора по указанному логину (он должен совпадать с логином у Git-провайдера). После первого входа переменную можно убрать.

Пишем .drone.yml: пайплайн шаг за шагом

Конфигурация пайплайна лежит в корне репозитория в файле .drone.yml. Базовый пример для Node.js-проекта с тестами и сборкой образа:

kind: pipeline
type: docker
name: default

steps:
  - name: install
    image: node:20-alpine
    commands:
      - npm ci

  - name: test
    image: node:20-alpine
    commands:
      - npm run test

  - name: build-image
    image: plugins/docker
    settings:
      repo: registry.example.com/myapp
      tags: ${DRONE_COMMIT_SHA:0:8}
      registry: registry.example.com
      username:
        from_secret: registry_username
      password:
        from_secret: registry_password
    when:
      branch:
        - main

trigger:
  branch:
    - main
    - develop
  event:
    - push
    - pull_request

Каждый step — это отдельный контейнер: install и test используют образ node:20-alpine заново (кеш зависимостей между шагами не переиспользуется автоматически, если явно не подключить volume или встроенный кеш-плагин). Секция build-image использует готовый плагин plugins/docker — это обёртка над docker build + docker push, которая сама разбирается с логином в реестр. Если вы уже развернули приватный Docker Registry на VPS, просто укажите его адрес в registry и repo.

Секция trigger определяет, на какие события и ветки реагировать. Без неё пайплайн запускается на всё подряд, включая push в любую фиче-ветку — обычно это не то, что нужно.

Проверить синтаксис файла без реального запуска можно локально через drone-cli:

drone lint .drone.yml

Секреты: пароли и токены отдельно от кода

Хранить пароль от реестра или токен деплоя прямо в .drone.yml нельзя — файл лежит в репозитории и виден всем, у кого есть к нему доступ. Секреты добавляются через интерфейс Drone (Repository → Settings → Secrets) или через drone-cli:

drone secret add \
  --repository yourorg/myapp \
  --name registry_password \
  --data "супер-секретный-пароль"

В пайплайне секрет подключается конструкцией from_secret, как в примере выше с registry_password. Значение никогда не попадает в логи сборки в открытом виде — Drone маскирует известные ему секретные строки при выводе.

Для деплоя по SSH тем же способом хранится приватный ключ:

drone secret add --repository yourorg/myapp --name ssh_key --data "$(cat ~/.ssh/deploy_key)"
  - name: deploy
    image: appleboy/drone-ssh
    settings:
      host: 203.0.113.10
      username: deploy
      key:
        from_secret: ssh_key
      script:
        - cd /opt/myapp && docker compose pull && docker compose up -d
    when:
      branch:
        - main
      event:
        - push

Секреты в Drone привязаны к конкретному репозиторию по умолчанию — это осознанное ограничение безопасности: скомпрометированный пайплайн одного проекта не даёт доступа к секретам другого. Если нужны общие секреты на несколько репозиториев, в self-hosted Drone это делается через organization secrets на уровне сервера (drone orgsecret), но начинайте с репозиторных — их проще держать под контролем.

HTTPS через reverse-proxy и типичные проблемы запуска

Drone сам не умеет TLS — эту работу отдают reverse-proxy. Если в стеке уже есть Traefik как reverse proxy для докера, добавьте лейблы к drone-server вместо проброса порта на хост:

  drone-server:
    image: drone/drone:2
    restart: unless-stopped
    networks:
      - traefik-net
    volumes:
      - drone-data:/data
    environment:
      - DRONE_SERVER_HOST=drone.example.com
      - DRONE_SERVER_PROTO=https
      # остальные переменные как выше
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.drone.rule=Host(`drone.example.com`)"
      - "traefik.http.routers.drone.tls.certresolver=letsencrypt"
      - "traefik.http.services.drone.loadbalancer.server.port=80"

networks:
  traefik-net:
    external: true

Без Traefik подойдёт обычный Nginx с проксированием на 127.0.0.1:8080 и сертификатом от certbot — принцип тот же: DRONE_SERVER_PROTO=https и реальный домен в DRONE_SERVER_HOST обязательны, иначе callback OAuth и вебхуки будут собираться по неверному адресу.

Частые проблемы при первом запуске:

СимптомПричинаРешение
no matching runner в логах пайплайнаRunner не законнектился к серверуСверьте DRONE_RPC_SECRET в обоих сервисах — значения должны совпадать посимвольно
Вебхук от GitHub не доходитСервер недоступен снаружи или неверный DRONE_SERVER_HOSTПроверьте, что домен резолвится и HTTPS отвечает: curl -I https://drone.example.com
Пайплайн виснет в статусе pendingУ runner закончилась DRONE_RUNNER_CAPACITY или закончились ресурсы хостаУвеличьте capacity или добавьте второй runner на другом сервере
permission denied при обращении к docker.sockRunner-контейнер запущен не тем пользователемУбедитесь, что volume /var/run/docker.sock:/var/run/docker.sock смонтирован, и не меняйте user: в сервисе runner
OAuth редиректит на 404Неверный callback URL в настройках приложенияCallback должен быть точно https://ваш-домен/login, без завершающего слеша и без порта

Runner под нагрузкой стоит держать на отдельном сервере от drone-server, чтобы сборки не съедали ресурсы у веб-интерфейса. Для этого укажите в DRONE_RPC_HOST внешний адрес сервера с drone-server вместо имени сервиса из compose и откройте порт между машинами (или пустите трафик через VPN).

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Чем Drone CI лучше Jenkins для небольшой команды?

Меньше эксплуатационных расходов: не нужно администрировать плагины, Java-окружение и агентов — весь стек это два Docker-образа, а конфигурация пайплайна лежит в репозитории, а не в веб-интерфейсе.

Можно ли использовать Drone с self-hosted Gitea?

Да, это одна из самых частых связок: и Drone, и Gitea легко разворачиваются в Docker Compose на одном сервере, OAuth настраивается за пять минут через раздел Applications в Gitea.

Нужен ли отдельный сервер под runner?

Не обязательно — для небольших проектов сервер и runner прекрасно живут на одной машине. Отдельный сервер под runner имеет смысл, когда сборки тяжёлые (компиляция, интеграционные тесты с базами) и не должны мешать веб-интерфейсу.

Что делать, если пайплайн собирает секреты в логи по ошибке?

Drone маскирует значения, добавленные через drone secret add, но не маскирует переменные, заданные напрямую в .drone.yml или переданные через echo внутри команды — никогда не выводите секреты явными командами вроде echo $TOKEN.

Поддерживает ли Drone мультиплатформенную сборку (arm64/amd64)?

Да, через plugins/docker с параметром platform или через отдельные пайплайны с platform.arch, но для этого runner должен уметь запускать buildx — на большинстве облачных VPS это работает из коробки при современной версии Docker.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →