Откуда задача: проблематика традиционного деплоя на VPS
В практике системного администрирования малых и средних проектных контуров распространен сценарий, при котором на одном виртуальном выделенном сервере под управлением операционной системы Linux разворачивается несколько нетребовательных сервисов, таких как серверная часть приложений, бота или статические веб-страницы. Инфраструктура хоста обычно включает Docker-демон и обратный прокси-сервер, представленный Caddy или Nginx. Стандартное требование к автоматизации процессов непрерывной интеграции и доставки заключается в выкатке новой версии сервиса на сервер при выполнении операции отправки изменений (git push) в основную ветку репозитория.
Развертывание приложений на собственном VPS традиционно выполняется по одной из трех ключевых схем, каждая из которых имеет выраженные технические компромиссы и уязвимости:
Прямой доступ из CI по протоколу SSH предполагает сохранение приватного ключа в настройках секретов репозитория. В ходе выполнения задачи workflow-раннер подключается к серверу по SSH и исполняет последовательность команд git pull && docker compose up -d --build. Подобный подход характеризуется высокой уязвимостью контура безопасности: приватный SSH-ключ является долгоживущим компрометируемым секретарем, а учетная запись на целевом сервере чаще всего обладает избыточными системными полномочиями. Компрометация секрета в любом из подключенных репозиториев предоставляет злоумышленнику неограниченный доступ к хост-системе, а не только к изолированному приложению.
Применение специализированных PaaS-панелей, таких как Coolify или Dokploy, обеспечивает удобный веб-интерфейс, однако приводит к разворачиванию крупного программного комплекса с собственной базой данных, модулями авторизации и расширенной поверхностью атаки исключительно ради задачи перезапуска нескольких контейнеров.
Использование легких веб-хуков с аутентификацией по статическому токену представляет собой вызов специализированного HTTP-эндпоинта, который по секрету в заголовке перезапускает сборку. Данный метод оставляет статичный секрет в CI-системе, не обеспечивая при этом надлежащего контроля над изоляцией процессов сборки и выполнения контейнера на стороне сервера.
Сформулированная инженерная задача требует построения системы непрерывного развертывания с жесткой моделью ограничений. Архитектура системы должна полностью исключать долгоживущие секреты из репозиториев CI, ограничивать права репозитория деплоем ровно одного подконтрольного приложения, принудительно изолировать процессы сборки и выполнения на стороне сервера, обеспечивать автоматический откат к предыдущей версии при сбоях и функционировать без привлечения внешних СУБД и веб-интерфейсов.
Что такое OIDC в CI и как работает OpenID Connect
OpenID Connect представляет собой открытый стандарт аутентификации, позволяющий сторонним системам подтверждать идентичность клиента на основе криптографически подписанных маркеров доступа. В системах непрерывной интеграции OIDC кардинально меняет подход к обеспечению безопасности: вместо использования статических паролей или ключей CI-платформа (GitHub Actions или GitLab CI) генерирует короткоживущий токен JSON Web Token (JWT), подписанный асимметричным приватным ключом издателя.
Структура JWT-токена содержит набор полей (claims), исчерпывающе фиксирующих контекст вызова задачи. Проверка подлинности на стороне целевого сервера осуществляется путем скачивания набора публичных ключей (JWKS) издателя по стандартизированному URI /.well-known/openid-configuration. Сервер проверяет криптографическую подпись, актуальность временных рамок и соответствие полей установленным правилам доступа.
Поле JWT (Claim) | Тип данных | Назначение и контекст безопасности |
| String | Уникальный URI издателя токена (например, |
| String | Запрошенный адрес получателя. Исключает использование токена на посторонних серверах. |
| String | Формализованная строка субъекта вызова, объединяющая репозиторий, ветку или окружение. |
| String | Идентификатор репозитория-источника в формате |
| String | Git-ссылка, вызвавшая запуск (например, |
| String | Имя целевого окружения развертывания (например, |
| Integer | Временные метки ограничения срока действия токена в интервале нескольких минут. |
Главным преимуществом использования OIDC является ликвидация риска утечки долгоживущих секретов: перехваченный токен сохраняет валидность в течение считанных минут, предназначен строго для конкретного URL-адреса сервера и не может быть применен для атаки на другие узлы инфраструктуры.
Что такое cd: архитектура и компонентная модель
Для реализации схемы деплоя на базе OIDC разработан специализированный self-hosted инструмент cd, предназначенный для обслуживания одиночных серверов Linux под управлением Docker. Кодовая база проекта доступна в открытом репозитории github.com/Dmitry-Fedotov-Dev/cd под лицензией MIT.
Архитектурно система состоит из трех компонентов:
Управляющий сервер cdd работает как системная служба systemd за обратным прокси-сервером. Демон принимает incoming-запросы по HTTP API, выполняет проверку подлинности OIDC-токенов, управляет сборкой контейнерных образов и осуществляет безопасную ротацию контейнеров.
Административная утилита cdctl предназначена для применения владельцем сервера. Инструмент позволяет регистрировать проекты, устанавливать лимиты системных ресурсов (RAM, CPU), генерировать блоки конфигурации для Caddy, просматривать текущую нагрузку и управлять состоянием сервисов.
Клиентский модуль deploy.sh интегрируется в конвейер CI. Для платформы GitHub Actions инструмент оформлен в виде готового Action, а для GitLab CI предоставляется соответствующий шаблон включает (include).
Исполняемые файлы cdd и cdctl написаны на языке Go без использования внешних библиотек: манифест go.mod не содержит сторонних зависимостей, а обработка JWT и обнаружение JWKS реализованы средствами стандартной библиотеки. Взаимодействие с движком Docker осуществляется через штатный CLI-интерфейс. Общий объем кодовой базы не превышает 2,7 тыс. строк, что обеспечивает легкую возможность аудита безопасности. Вся информация о проектах хранится в виде обычных JSON-файлов в директории /etc/cdd/projects, а текущее состояние фиксируется в файловой системе /var/lib/cdd без использования СУБД.
Как это работает: пошаговый механизм деплоя
Процесс отправки и развертывания приложения на сервере делится на пять строго регламентированных шагов.

На первом этапе CI-задача запрашивает OIDC-токен у платформы. В GitHub Actions для этого объявляются права permissions: id-token: write и contents: read. Клиент запрашивает JWT с полем aud, соответствующим публичному адресам управляющего сервера.
На втором этапе клиентский скрипт выполняет архивацию исходного кода командой git archive и отправляет полученный tar.gz на сервер методом POST по адресу /v1/deploy?project=app&sha=<commit> с передачей JWT в заголовке Authorization: Bearer <token>. Сервер потоком отдаёт логи сборки в реальном времени и завершает трансляцию строкой с результатом деплоя.
Код результата | Системный статус | Расшифровка состояния операции |
0 |
| Новая версия успешно собрана, прошла проверку здоровья и введена в эксплуатацию. |
1 |
| Сбой сборки или старта. Система автоматически запустила предыдущий рабочий релиз. |
2 |
| Архив отклонен из-за наличия запрещенных файлов или превышения лимитов. |
3 |
| Сервер занят обработкой другого деплоя или испытывает дефицит ресурсов. |
4 |
| Некорректная структура HTTP-запроса или ошибочные параметры. |
5 |
| Ошибка проверки подписи токена, истечение срока или несоответствие правил доступа. |
На третьем этапе сервер cdd выполняет валидацию токена: проверяется подпись RS256 по кэшированным ключам JWKS, принадлежность iss к списку разрешенных издателей, точное совпадение aud с публичным URL сервера и соответствие временных рамок exp/nbf. Затем система проверяет сопоставление претензий (repository, ref, environment) с правилами проекта, заданными администратором сервера.
На четвертом этапе сервер распаковывает архив и производит сборку. Способ сборки полностью определяется шаблоном сервера, а не кодом из репозитория. Архив подвергается проверке на отсутствие символических и жестких ссылок, абсолютных путей или попыток выхода за пределы каталога (Zip Slip).
Тип шаблона | Порядок сборки образа | Стратегия запуска |
| Выполнение | Исполнение команды |
| Размещение файлов напрямую или сборка через | Подача файлов через встроенный Caddy |
| Использование Dockerfile проекта (активируется только администратором) | Команда, заданная в Dockerfile |
Сборка осуществляется в изолированном BuildKit-контейнере с ограничением ресурсов в 1 ГБ ОЗУ, 1 vCPU и лимитом времени в 15 минут, без доступа к секретам сервера и файловой системе хоста.
На пятом этапе происходит запуск и автоматическая ротация. Сервер поднимает новый контейнер, биндит его порт на 127.0.0.1 и выполняет опрос маршрута проверки здоровья (по умолчанию /healthz) в течение интервала до 90 секунд. При получении успешного ответа старый контейнер останавливается и удаляется. В случае сбоя новая версия уничтожается, в лог CI выводятся причины падения, а старый контейнер мгновенно возвращается в работу.
Безопасность и изоляция: hardening контейнеров по умолчанию
Центральным элементом архитектуры cd является принудительная изоляция исполняемых контейнеров, параметры которой устанавливаются на стороне сервера и не могут быть переопределены из репозитория приложения.
Каждый контейнер запускается от имени непривилегированного пользователя с UID 10001. Корневая файловая система монтируется в режиме только для чтения (read-only), а запись разрешается исключительно в создаваемый в оперативной памяти каталог /tmp и в явно выделенный том данных проекта. У контейнера принудительно сбрасываются все привилегия Linux (--cap-drop=ALL), активируется флаг no-new-privileges, жестко ограничиваются лимиты CPU, RAM и количество процессов без возможности использования файла подкачки (swap). Контейнеры изолируются в собственных Docker-сетях, а их порты публикуются строго на интерфейсе 127.0.0.1, исключая прямое обращение из внешней сети в обход обратного прокси-сервера.
Ниже приведен пример конфигурационного файла GitHub Actions workflow, настраиваемого в репозитории приложения для взаимодействия с сервером деплоя:
YAML
permissions:
contents: read
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: Dmitry-Fedotov-Dev/cd@v1
with:
server: ${{ vars.CD_SERVER }}
project: appНа стороне сервера администратор привязывает проект к конкретным параметрам авторизации и ограничениям ресурсов с помощью утилиты управления:
Bash
sudo cdctl project add app-repo me/app \
--branch main \
--environment production \
--type node \
--domain app.example.com \
--data /app/data \
--memory 256mПоскольку демон cdd функционирует с правами root для взаимодействия с сокетом Docker, сама служба изоляции ограничена правилами systemd на уровне хост-системы:
Ini, TOML
[Service]
ExecStart=/usr/local/bin/cdd -config /etc/cdd/cdd.json
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=full
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictSUIDSGID=yes
LockPersonality=yes
Дополнительные возможности инструмента
Помимо основного конвейера деплоя инструмент cd включает ряд встроенных функций для упрощения эксплуатации хоста.
Система обеспечивает автоматическую настройку доменных имен при использовании провайдера Porkbun и прокси Caddy. Команда cdctl domain setup сохраняет API-ключ в файл с доступом только для root. При добавлении проекта с флагом --domain утилита автоматически создает A-запись в DNS, формирует конфигурационный блок Caddyfile, проверяет его валидность и перезагружает веб-сервер Caddy без затронет сторонних записей.
В инструмент заложена функция моментального клонирования проектов. Команда cdctl project clone shop-anna --from shop создает независимый экземпляр приложения со собственным сетевым портом, изолированным env-файлом, отдельным каталогом данных и доменным именем. При отправке обновления в основной репозиторий shop релиз автоматически раскатывается на все созданные клоны.
Подключение новых репозиториев к серверу упрощено до исполнения скрипта init.sh, который самостоятельно генерирует файл workflow, прописывает переменную CD_SERVER в настройках репозитория и инициирует первый автоматический деплой.
Сильные стороны архитектуры
Анализ архитектурных решений cd позволяет выделить группу существенных преимуществ перед стандартными подходами:
Полное исключение долгоживущих секретов в CI гарантирует безопасность инфраструктуры даже при полной утечке переменной среды репозитория. Временный OIDC-токен невозможно использовать повторно или применить для атаки на другие серверы.
Четко очерченная граница доверия жестко ограничивает полномочия репозитория. Код проекта может инициировать деплой, читать свои логи и запускать откат, но не способен изменить выделенные ресурсы, открываемые порты или правила доступа к серверу.
Принудительный hardening исключает человеческий фактор: администратору не требуется вручную прописывать директивы безопасности в каждом конфигурационном файле docker-compose.yml. Сервер назначает ограничения безопасности автоматически при старте контейнера.
Автоматический откат без участия человека предотвращает неработоспособность продакшена при выкатке битых бинарников или упавших при старте скриптов. Если приложение не ответило на health-check, система мгновенно восстанавливает прошлый рабочий контейнер.
Минималистичность и автономность выражаются в полном отсутствии тяжелых веб-панелей, сторонних баз данных и фоновых фоновых агентов. Демон сервера практически не потребляет ресурсов хоста, а бинарники не имеют внешних зависимостей.
Слабые стороны, ограничения и компромиссы
Архитектура cd содержит ряд осознанных инженерных ограничений, которые необходимо учитывать перед внедрением.
Система полностью лишена графического веб-интерфейса. Управление, мониторинг и анализ логов выполняются исключительно через CLI-интерфейс cdctl на сервере или через вызовы операций в workflow.
Деплой выполняется с технологическим перерывом в обслуживании (downtime). Старый контейнер останавливается до старта нового из-за использования единого порта на 127.0.0.1. Время простоя составляет от 1,2 до 2,5 секунд для быстрых сервисов на Go и может быть существенно выше для приложений с долгим временем инициализации.
На сервере действует ограничение на один параллельный деплой. Сборки выполняются строго по очереди; при поступлении второго запроса во время активности первого сервер возвращает код 3 busy, что приводит к завершению второй CI-задачи с ошибкой.
Модель системы ограничена схемой «один проект — один контейнер». Вложенный запуск docker compose, сайдкаров и управляемых баз данных не поддерживается: СУБД Redis или PostgreSQL должны разворачиваться администратором отдельно.
Служба cdd исполняется с правами root из-за необходимости работы с Docker CLI. В случае обнаружения критической уязвимости в коде распаковщика или валидатора токенов нападающий может получить доступ к хост-системе.
Автоматизация доменов ограничена парой Porkbun и Caddy. При использовании DNS-провайдеров Cloudflare, Route53 или прокси Nginx конфигурационные файлы и записи DNS приходится формировать вручную.
Почему приняты именно такие инженерные решения
Отказ от бесшовного переключения (blue-green) в пользу секундного простоя продиктован стремлением сохранить простую сетевую архитектуру. Организация blue-green деплоя требует динамо-управления конфигурацией обратного прокси-сервера или внедрения сложного роутера. Для некритичных сервисов и ботов кратковременная пауза при выкатке релиза является разумным компромиссом.
Изоляция базы данных от сервиса сборки защищает инфраструктуру от случайного повреждения информации. База данных требует собственного цикла резервного копирования и обслуживания. Ограничение роли cd только развертыванием кода гарантирует, что git push не сможет пересоздать или очистить тома с данными.
Выдача статуса 3 busy вместо построения серверной очереди предотвращает зависание HTTP-соединений в CI и экономит ресурсы хоста. Логика повтора перекладывается на CI-платформу, где ее легко контролировать стандартными средствами перезапуска или настройкой concurrency.
Сравнение с альтернативными инструментами
Для правильного выбора инструмента непрерывной доставки рекомендуется сопоставить cd с популярными аналогами на рынке.
Инструмент | Механизм аутентификации в CI | Долгоживущий секрет в CI? | Деплой без простоя (Zero-downtime)? | Требования к серверу | Изоляция контейнера по умолчанию |
cd | OIDC-токен с валидацией claims | Нет | Нет (простой 1–3 сек) | Docker, systemd, Caddy/Nginx | Жесткая, задается сервером |
SSH + Compose | SSH-ключ доступа | Да (приватный ключ) | Зависит от настройки Compose | SSH-сервер, Docker Compose | Определяется в |
Dokku | Git push по SSH | Да (SSH-ключ) | Да (через checks) | Dokku, Nginx, Buildpacks | Стандартный Docker |
Kamal | SSH-доступ + Registry | Да (SSH + Registry Key) | Да (через kamal-proxy) | Docker, kamal-proxy | Стандартный Docker |
Coolify / Dokploy | Webhook или GitHub App | Да (Webhook token) | Да (через health-check) | PostgreSQL, PaaS-панель | Настраивается в UI |
Комплексные PaaS-панели (Coolify, Dokploy) предлагают богатый функционал с СУБД в клик и веб-интерфейсом, однако требуют значительных ресурсов хоста для работы самой панели. Dokku предоставляет проверенную временем экосистему плагинов, но использует SSH-ключи для авторизации. Kamal отлично подходит для кластеров и обеспечивают деплой без простоя, но требует наличия внешнего Docker Registry и SSH-доступа. Обычная связка SSH и Docker Compose обеспечивает абсолютную гибкость, но оставляет риски утечки долгоживущих ключей.
Для кого предназначен инструмент и где лучше выбрать альтернативы
Внедрение cd оправдано в следующих инфраструктурных сценариях:
Наличие одного VPS с несколькими нетяжелыми сервисами (Node.js, Go, статика), разворачиваемыми из GitHub Actions или GitLab CI.
Категорическое требование по исключению долгоживущих SSH-ключей и секретов из настроек CI-платформы.
Необходимость разграничения прав, при которой каждый репозиторий управляет только собственным контейнером.
Готовность команды к административной работе через CLI и допустимость кратковременного простоя при выкатке обновления.
Потребность в создании быстро редактируемых клонов одного приложения для разных клиентов на едином сервере.
Выбор альтернативных решений предпочтителен в случаях:
Потребности в визуальной веб-панели управления с графиками и кнопками (рекомендуются Coolify или Dokploy).
Необходимости разворачивания готовых связок баз данных и инфраструктурных сервисов из шаблонов (рекомендуются Dokku или PaaS).
Бескомпромиссных требований к zero-downtime деплою или работе на нескольких серверах одновременно (рекомендуется Kamal).
Предоставления хостинга малознакомым пользователям без доверия к коду (cd не лимитирует дисковый ввод-вывод и сетевой трафик).
Итог
Инструмент cd эффективен при решении узкоспециализированной задачи: обеспечение безопасной автоматической доставки приложений на изолированный VPS по git push без использования долгоживущих секретов в CI. Перенос концепции OIDC-доверия с крупных облачных провайдеров на обычные выделенные серверы позволяет получить высочайший уровень защиты access-контура. В сочетании с жесткими принудительными флагами изоляции контейнеров, изолированной сборкой в BuildKit и автоматическим откатом система предоставляет надежную альтернативу как громоздким PaaS-панелям, так и опасным SSH-скриптам.

Комментарии 0