Перейти к основному содержимому
  1. Posts/
  2. Self-Hosting/

GitOps для homelab: разворачиваем Forgejo и Komodo с нуля

·4939 слов·24 минут· loading · loading · ·
Stilicho2011
Автор
Stilicho2011
Пишу о homelab, self-hosting, автоматизации и open-source решениях
Оглавление
Self-Hosting - This article is part of a series.
Part : This Article

Рано или поздно любой домашний сервер с десятком Docker-контейнеров превращается в набор compose-файлов, которые лежат у тебя прямо на хосте и правятся руками с помощью твоего любимого редактора. Работает? Да! Но есть нюанс. Если что-то сломалось после правки, непонятно, что именно изменилось и как вернуть все “взад”. А если стеков много? А Хостов? Правок было десять за месяц? Восстановить историю событий по памяти уже нереально.

В этой статье я постараюсь подробно разобрать, как решить эту проблему с помощью так называемого подхода GitOps - на примере связки Forgejo (собственный git-сервер) и Komodo (панель управления Docker-стеками). Всё будем ставить с нуля, разбирая каждый шаг и каждый параметр конфигурации - этого будет достаточно, чтобы не просто скопировать команды, а понимать, что именно они делают и почему.

Если Komodo у вас уже стоит и настроена - например, вы недавно переезжали на неё с Portainer, я разбирал это отдельно в статье «Переезжаем с Portainer на Komodo: полная установка и настройка» - эту часть можно пропустить и начинать прямо с шага про Forgejo ниже. А если, наоборот, интересен не абстрактный туториал с нуля, а то, как именно эта связка живёт у меня в реальной инфраструктуре - с настоящими доменами, переносом уже существующих стеков в git и вебхуком, который уже работает на проде, - об этом отдельная статья: «Forgejo + Komodo: превращаем Docker Compose в GitOps».

Note

Статья рассчитана на тех, кто раньше не сталкивался с GitOps и/или с Forgejo и Komodo по отдельности. Предполагаю, что базовые знания Docker у пользователя есть..

Что такое GitOps и зачем он в homelab
#

Классический способ управления Docker-стеками выглядит так:

 flowchart LR

```
A[Правите docker-compose.yml на сервере] --> B[docker compose up -d]
B --> C{Что-то сломалось?}
C -->|Да| D[Пытаетесь вспомнить, что меняли]
C -->|Нет| E[Всё работает, пока не забудете]
```

Проблема в том, что сервер сам по себе - не источник правды (иностранный термин, но кладезь информации звучит еще хуже). История изменений живёт только у вас в голове (или не живёт вовсе, если головы нет). Предположим ты правил конфиг в 23:00 после тяжелого рабочего дня, как песни банды Зе Битлз, а через неделю что-то сломалось - шансы понять и вспомнить точную причину стремятся к нулю.

GitOps переворачивает эту схему: единственным источником (да-да, опять) правды становится git-репозиторий. Все compose-файлы лежат в нём, а на сервере работает механизм, который:

  1. Получает уведомление о новом коммите (через webhook) или сам периодически проверяет репозиторий;
  2. Подтягивает изменения (git pull);
  3. Применяет их - пересоздаёт нужные контейнеры (docker compose up -d).
 flowchart LR

```
A[Меняете compose-файл локально] --> B[git commit + push]
B --> C[Репозиторий получает webhook]
C --> D[Komodo делает Pull + Deploy]
D --> E[Контейнеры обновлены]
```

Важный нюанс: сервер сам по себе никогда не принимает решение, что именно должно быть развёрнуто. Он просто синхронизирует своё состояние с тем, что записано в git. Если файл в репозитории не менялся - ничего и не произойдёт, даже если процедура деплоя запустится вручную ещё раз.

Что это даёт на практике:

  • История изменений. git log показывает, кто, когда и что поменял, а git diff - точно что именно изменилось между любыми двумя версиями.
  • Откат в один шаг. Ошиблись - git revert, push, и старая версия конфига снова развёрнута, без ручного редактирования файлов на сервере.
  • Единая точка правды. Не нужно помнить, на каком именно хосте лежит актуальная версия файла - она всегда там, в репозитории.
  • Воспроизводимость. Если сервер придётся поднимать заново (авария, переезд на новое железо), весь набор конфигов уже описан в git - не нужно вспоминать, что и как было настроено.

Для homelab с одним сервером выгода не так очевидна, как в продакшене с командой из десяти инженеров - но привычка появится быстро, а откатить неудачный эксперимент одной командой действительно удобно, особенно если ты, как и многие в домашней инфраструктуре, экспериментируешь с конфигами регулярно.

Состав ингредиентов
#

  • хост с установленными Docker;
  • немного свободного места на диске - Forgejo и Komodo сами по себе лёгкие сервисы, основной расход места придётся на данные MongoDB и историю git-репозиториев;
  • (опционально, но рекомендуется) уже настроенный реверс-прокси вроде Traefik, если хотите вешать сервисы на собственные домены вместо голых портов - в статье будет пример именно с ним.

Для базы данных Forgejo используем встроенный SQLite - этого более чем достаточно для домашнего использования и позволяет не отвлекаться на настройку внешнего PostgreSQL на старте. Для Komodo Core потребуется MongoDB - это единственная официально поддерживаемая СУБД для Core.

Шаг 1. Поднимаем Forgejo - свой git-сервер
#

Forgejo - форк Gitea, лёгкий self-hosted git-сервер с интерфейсом, похожим на GitHub: репозитории, issues, pull request’ы, Actions (CI/CD), встроенный веб-редактор файлов. Для наших целей нужна только базовая функциональность - хранение репозитория и webhooks.

Создаём папку для проекта, например /home/stilicho/docker/forgejo, и в ней файл docker-compose.yml:

services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:15
    container_name: forgejo
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - FORGEJO__database__DB_TYPE=sqlite3
      - FORGEJO__webhook__ALLOWED_HOST_LIST=external,private
    restart: unless-stopped
    volumes:
      - ./forgejo-data:/data
    ports:
      - "3000:3000"
      - "2222:22"

Разберём каждый параметр:

  • USER_UID / USER_GID - под каким пользователем внутри контейнера будет работать Forgejo и, соответственно, каким владельцем будут создаваться файлы в примонтированном томе ./forgejo-data. Если у вас на хосте основной пользователь имеет другой UID (проверить можно командой id), лучше подставить его значение - так избежите проблем с правами доступа при бэкапах или ручном редактировании файлов внутри forgejo-data.
  • FORGEJO__database__DB_TYPE=sqlite3 - явно указываем использовать SQLite. Без этой переменной Forgejo в интерактивном мастере предложит выбор БД, но с ней шаг выбора в мастере уже будет предзаполнен.
  • FORGEJO__webhook__ALLOWED_HOST_LIST=external,private - без этой переменной у Forgejo включена защита от SSRF, и по умолчанию она пропускает webhook-запросы только на публичные IP-адреса (external). Если Komodo крутится в той же локальной сети, что и Forgejo (а в домашней инфраструктуре так почти всегда и есть), домен komodo.stilicho.ru будет резолвиться в адрес из локальной сети (192.168.x.x и т.п.) - и без ключевого слова private в списке Forgejo будет отклонять доставку webhook с ошибкой вида webhook can only call allowed HTTP servers ... deny 'komodo.stilicho.ru(192.168.x.x:443)', даже если сам webhook настроен полностью верно.
  • ./forgejo-data:/data - bind mount, в котором Forgejo хранит вообще всё: базу данных SQLite, репозитории, конфиги, вложения issues. Это единственная папка, которую достаточно бэкапить, чтобы иметь полную копию инстанса.
  • Порт 3000 - веб-интерфейс (HTTP).
  • Порт 2222:22 - SSH-доступ к git (git clone git@<host>:2222/...). Пробрасываем именно на нестандартный внешний порт 2222, чтобы не конфликтовать с SSH-демоном самого хоста, который почти наверняка уже слушает 22.

Запускаем:

docker compose up -d

Проверить, что контейнер поднялся и не падает в перезапуск, можно командой docker compose logs -f forgejo - в первые секунды после старта Forgejo инициализирует структуру данных в /data, это нормально.

Дальше настраиваем через веб-интерфейс:

  1. Открываем http://<адрес-хоста>:3000 - попадаем в мастер первоначальной настройки. Если позже захотите повесить Forgejo на собственный домен через Traefik (как будет показано ниже для Komodo), это делается аналогичным способом - добавлением labels в compose-файл.
  2. В мастере большинство полей уже предзаполнены (в том числе тип БД - благодаря переменной окружения выше). Стоит обратить внимание на поле Forgejo Base URL - если планируете доступ через домен, впишите его сюда сразу, чтобы избежать перенастройки позже (ссылки на клонирование репозиториев в интерфейсе строятся именно из этого значения).
  3. Пролистываем до раздела Administrator Account Settings - здесь создаём первого пользователя. Он автоматически станет администратором всего инстанса (в нашем примере - логин stilicho).
  4. Жмём Install Forgejo внизу страницы и ждём завершения инициализации - обычно занимает несколько секунд.
  5. Входим под только что созданным аккаунтом.

Создаём тестовый репозиторий: значок + в правом верхнем углу → New Repository. Заполняем форму:

  • Repository Name - например, youtube (репозиторий, где будут храниться compose-файлы для стеков);
  • Visibility - обязательно отмечаем Private, если в репозиторий планируется класть .env-файлы с секретами (паролями, токенами);
  • галочку Initialize Repository стоит поставить - так сразу появится README.md, и репозиторий не будет полностью пустым (в пустой репозиторий немного неудобнее делать первый git clone/push).
Tip

На этом этапе полезно сразу положить в репозиторий один простой compose-файл - например, разворачивающий тестовый контейнер nginx или traefik/whoami. Его мы будем деплоить через Komodo на следующих шагах, чтобы проверить всю цепочку на живом примере, а не абстрактно.

Шаг 2. Поднимаем Komodo
#

Komodo - панель для управления Docker-стеками на одном или нескольких хостах, с поддержкой git-репозиториев как источника compose-файлов и автоматизации через Procedures. Состоит из трёх компонентов: Core (веб-интерфейс и API, “мозг” системы), база данных (MongoDB) и Periphery (агент, который непосредственно выполняет команды Docker на хосте).

Заводим проект в /home/stilicho/docker/komodo - по тому же принципу, по которому организованы остальные стеки на хосте: все compose-проекты у нас лежат в одной родительской папке /home/stilicho/docker, и Komodo дальше будет видеть их все через эту папку (подробнее - в описании PERIPHERY_ROOT_DIRECTORY ниже).

docker-compose.yml для Komodo:

services:
  komodo-core:
    image: ghcr.io/moghtech/komodo-core:latest
    container_name: komodo-core
    restart: unless-stopped
    environment:
      - KOMODO_DATABASE_ADDRESS=komodo-db:27017
      - KOMODO_DISABLE_CONFIRM_DIALOG=true
      - KOMODO_WEBHOOK_SECRET=CHANGE_ME_TO_A_LONG_RANDOM_STRING
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.komodo.rule=Host(`komodo.stilicho.ru`)"
      - "traefik.http.routers.komodo.entrypoints=websecure"
      - "traefik.http.routers.komodo.tls.certresolver=letsencrypt"
      - "traefik.http.services.komodo.loadbalancer.server.port=9120"
    networks:
      - komodo
      - proxy
    depends_on:
      - komodo-db

  komodo-db:
    image: mongo:7
    container_name: komodo-db
    restart: unless-stopped
    volumes:
      - ./komodo-db:/data/db
    networks:
      - komodo

  komodo-periphery:
    image: ghcr.io/moghtech/komodo-periphery:latest
    container_name: komodo-periphery
    restart: unless-stopped
    network_mode: host
    environment:
      - PERIPHERY_ROOT_DIRECTORY=/home/stilicho/docker
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /home/stilicho/docker:/home/stilicho/docker

networks:
  komodo:
  proxy:
    external: true

Разберём построчно, что здесь происходит и зачем.

komodo-core
#

  • KOMODO_DATABASE_ADDRESS=komodo-db:27017 - адрес MongoDB внутри Docker-сети komodo. Обратите внимание: имя хоста komodo-db - это имя сервиса из этого же compose-файла, Docker DNS резолвит его автоматически внутри общей сети, отдельно ничего прописывать не нужно.
  • KOMODO_DISABLE_CONFIRM_DIALOG=true - отключает всплывающее окно подтверждения перед каждым действием (деплой, рестарт и т.д.) в веб-интерфейсе. Удобно для домашнего использования, где вы и так единственный оператор; в команде, где действия совершают несколько человек, диалог подтверждения может, наоборот, стоит оставить включённым.
  • KOMODO_WEBHOOK_SECRET - секрет, которым Komodo проверяет подлинность входящих webhook-запросов от git-провайдера (по подписи X-Hub-Signature-256). Это один общий секрет на весь инстанс Core, а не отдельное значение под каждый Repo/Stack/Procedure - при настройке webhook в Forgejo для любого ресурса вы будете указывать именно это значение. Сгенерировать случайную строку можно, например, командой openssl rand -hex 32.
  • traefik.enable=true и последующие labels - вместо того, чтобы пробрасывать порт 9120 наружу напрямую, отдаём маршрутизацию Traefik. Строка Host(\komodo.stilicho.ru`)- правило, по какому домену Traefik должен направлять трафик именно на этот контейнер;certresolver=letsencrypt- какой резолвер сертификатов использовать (имя должно совпадать с тем, что уже настроено в вашем Traefik);loadbalancer.server.port=9120` - на какой внутренний порт контейнера направлять запросы (это порт, на котором слушает сам Komodo Core, он не меняется).
  • networks: komodo, proxy - Core должен быть одновременно в сети komodo (чтобы достучаться до komodo-db) и в сети proxy (это внешняя сеть, в которой сидит ваш Traefik - именно через неё Traefik получает доступ к контейнеру для маршрутизации).
Warning

Название proxy в блоке networks: proxy: external: true должно точно совпадать с именем docker-сети, в которой у вас уже работает Traefik. Если у вас эта сеть называется иначе (например, traefik-proxy или web), обязательно поправьте это имя в обоих местах - иначе Traefik просто не увидит контейнер Komodo и маршрутизация не заработает, а docker compose up выдаст ошибку о несуществующей внешней сети.

komodo-db
#

Ничего специфичного - обычный контейнер MongoDB 7 с данными на bind mount ./komodo-db:/data/db, чтобы база физически лежала рядом с остальными файлами проекта, а не пряталась в /var/lib/docker/volumes с автогенерированным именем. Отдельно в сеть proxy этот сервис не добавляем - снаружи он никому, кроме komodo-core, не нужен.

komodo-periphery
#

Это самая важная для понимания часть.

  • network_mode: host - Periphery работает в сетевом пространстве самого хоста, а не в изолированной Docker-сети. Это осознанный выбор: агенту нужно уметь взаимодействовать с Docker так же, как это делали бы вы сами по SSH, и режим host избавляет от лишних сложностей с проброской портов между агентом и Core.
  • PERIPHERY_ROOT_DIRECTORY=/home/stilicho/docker - ключевая переменная. Она указывает Periphery-агенту на родительскую папку, где лежат директории всех остальных стеков (не только Komodo). Именно благодаря ей Komodo видит и умеет управлять уже существующими compose-проектами на хосте, а не только тем, что создан специально под неё. По сути, это “корень”, от которого Komodo строит все остальные пути при работе со Stack-ресурсами.
  • /var/run/docker.sock:/var/run/docker.sock - сокет Docker-демона хоста, примонтированный внутрь контейнера Periphery. Без него агент физически не имеет возможности ни разворачивать, ни останавливать, ни просматривать логи каких-либо контейнеров - вся команда docker compose ..., которую в итоге выполняет Periphery, обращается именно к этому сокету.
  • /home/stilicho/docker:/home/stilicho/docker - второй bind mount, дающий контейнеру доступ непосредственно к файлам на диске (compose-файлам, .env, любым вспомогательным файлам стеков).
Note

Если у вас, как и здесь, Periphery работает в network_mode: host, то бинд-маунт /home/stilicho/docker:/home/stilicho/docker должен указывать на тот же путь, что и PERIPHERY_ROOT_DIRECTORY - то есть путь внутри контейнера и путь на хосте совпадают буква в букву. Причина в том, что Periphery в итоге выполняет команды docker compose, обращаясь к путям на файловой системе хоста напрямую (через сокет), а не к путям внутри своего контейнера - если пути разъедутся, агент будет “видеть” файлы у себя в контейнере, но не сможет сопоставить их с реальным расположением на хосте, и деплой будет падать с ошибкой “файл не найден” или похожей.

Warning

Если на хосте уже есть папки с работающими стеками - ничего страшного, ничего не сломается. PERIPHERY_ROOT_DIRECTORY - это область видимости, а не команда что-то сделать: она позволяет Periphery видеть compose-проекты в указанной папке (Komodo сможет предложить их в интерфейсе), но сама по себе ничего не запускает, не останавливает и не изменяет. Существующие стеки продолжат работать точно так же, как работали, пока вы явно не создадите под них Stack-ресурс в Komodo и не запустите Deploy.

Единственное, о чём стоит помнить: если вы решите взять уже существующий стек под управление через git (привязать к Repo-ресурсу и настроить Pull), Komodo при следующем деплое будет ориентироваться на то, что лежит в репозитории - а не на то, что вы могли поправить руками на диске после последнего коммита. Незапушенные локальные правки в этом случае потеряются при пересоздании контейнера. Поэтому для стеков, переведённых под GitOps, правило простое: с этого момента конфиг правим только через git, не руками на хосте.

Запускаем:

docker compose up -d

Первый запуск может занять чуть больше времени - MongoDB инициализирует структуру данных, а Core ждёт, пока база станет доступна (за это отвечает depends_on, но он гарантирует только порядок запуска контейнеров, не готовность самой БД принимать соединения - если Core с первой попытки не подключится, он должен переподключиться сам в течение нескольких секунд; если нет - docker compose restart komodo-core).

Открываем https://komodo.stilicho.ru (или http://<адрес-хоста>:9120, если решили пока обойтись без Traefik и оставили порт проброшенным напрямую), создаём первого пользователя-администратора в форме, которая появляется при первом заходе.

Tip

Если в инфраструктуре уже есть свой OIDC-провайдер (например, Authentik), Komodo умеет входить через него - это удобно, когда доступ к панели нужно давать не только себе. Настраивается отдельными переменными окружения для komodo-core (адрес провайдера, client ID/secret) и не является обязательным для первого запуска - можно вернуться к этому позже, когда базовая схема уже будет работать.

Шаг 3. Связываем Komodo с репозиторием в Forgejo
#

Чтобы Komodo могла клонировать приватный репозиторий (а публичный, кстати, тоже - Forgejo по умолчанию требует авторизацию даже для чтения по HTTP, если явно не открыть анонимный доступ), нужен токен доступа.

Создаём токен в Forgejo
#

  1. Заходим под своим пользователем (stilicho) в Forgejo → значок профиля в правом верхнем углу → Settings.

  2. В левом меню выбираем Applications.

  3. В разделе Manage Access Tokens заполняем Token Name (например, komodo-readonly).

  4. В блоке Repository and Organization Access - это нововведение именно 15-й версии - выбираем один из трёх вариантов:

    • All - токен получит доступ ко всем репозиториям аккаунта (публичным, приватным, с ограниченным доступом);
    • Public only - доступ ограничен публичными репозиториями;
    • Specific repositories - доступ только к явно выбранным репозиториям.

    Для нашей задачи логичнее всего взять Specific repositories и выбрать в появившемся списке только youtube - токен, скомпрометированный или случайно засвеченный где-то ещё, в этом случае не даст доступа ни к чему, кроме одного этого репозитория.

  5. При выборе Specific repositories доступны только четыре права: read:repository, write:repository, read:issue, write:issue (остальные скоупы для точечного токена Forgejo просто не показывает - им не с чем сверяться, если токен не привязан к конкретному репозиторию). Для клонирования и pull со стороны Komodo достаточно read:repository; write:repository берём дополнительно, только если планируете и пушить в этот репозиторий тем же токеном (например, для автоматических коммитов из CI).

  6. Жмём Generate Token.

  7. Токен показывается один единственный раз, сразу после генерации. Скопируйте его сразу - если закроете страницу или обновите её, посмотреть токен повторно будет уже нельзя, придётся генерировать новый.

Регистрируем токен в Komodo
#

Komodo не привязывает токен к конкретному репозиторию напрямую - вместо этого сначала регистрируется учётная запись у git-провайдера (связка “домен + логин + токен”), а уже она потом используется при создании любого количества Repo-ресурсов.

  1. В интерфейсе Komodo идём в Settings (иконка шестерёнки) → раздел Providers.
  2. В блоке Git Providers жмём Add Provider (или +, в зависимости от версии интерфейса). Заполняем:
    • Domain - домен вашего Forgejo без протокола и без http(s)://, например forgejo.stilicho.ru (или <адрес-хоста>:3000, если работаете без домена);
    • HTTPS - переключатель, включён по умолчанию; если Forgejo пока не за Traefik и доступна только по http://, обязательно выключите этот тумблер - иначе Komodo будет пытаться клонировать по https:// и получит ошибку соединения;
    • внутри провайдера добавляем аккаунт (Add Account): Username - ваш логин в Forgejo (stilicho), Token - токен, скопированный на предыдущем шаге.
  3. Сохраняем - провайдер и привязанный к нему аккаунт появятся в списке.
Note

На стороне Komodo токен привязывается не к конкретному Repo-ресурсу, а к домену провайдера в целом - один раз добавили Forgejo как провайдер и аккаунт stilicho, и дальше при создании новых Repo-ресурсов для других репозиториев на том же Forgejo просто выбираете этот аккаунт из списка, а не копируете токен заново в каждую форму. А вот на стороне самого Forgejo, как мы только что видели, токен в v15 можно ограничить конкретным репозиторием - это два независимых уровня ограничения, и они друг другу не мешают.

Создаём Repo-ресурс
#

  1. Resources → Repos → Create Repo.
  2. Даём ресурсу понятное имя (например, `youtube).
  3. В поле Repo указываем только владелец/репозиторий, без протокола и домена - в нашем случае stilicho/youtube. Домен Komodo подставит сама из выбранного на следующем шаге Git-провайдера; если вписать сюда ещё и полный URL (https://forgejo.stilicho.ru/stilicho/youtube.git), домен задвоится и клонирование упадёт с ошибкой remote: Not found - Komodo в буквальном смысле склеит <домен провайдера>/<то, что вы ввели>.
  4. В поле Git Account выбираем из выпадающего списка аккаунт stilicho на провайдере, добавленном на предыдущем шаге - сам токен здесь заново вводить не нужно, Komodo подставит его автоматически при клонировании.
  5. Ветку (Branch) оставляем main (или master, в зависимости от того, как Forgejo назвала ветку по умолчанию при создании репозитория - это видно на странице репозитория).
  6. Сохраняем.

Проверить, что клонирование прошло успешно, можно на странице самого Repo-ресурса - там есть история выполненных операций (Pull, Clone) с логами, включая точную команду git clone, которую Komodo выполнила. Полезно проверить ее выполнение руками хотя бы один раз - так виднее, что именно подставилось вместо домена и токена. Если авторизация не прошла, а лог покажет ошибку 401/403 - стоит перепроверить, что токен скопирован без лишних пробелов и что права токена включают чтение репозитория. Если же ошибка вида remote: Not found / repository ... not found при, казалось бы, верном токене - почти наверняка домен в адресе задвоился именно из-за полного URL в поле Repo (см. предупреждение выше).

Tip

Если на хосте уже есть несколько работающих стеков (как в примере выше, где PERIPHERY_ROOT_DIRECTORY указывает на общую папку /home/stilicho/docker), удобнее не заводить отдельный репозиторий под каждый стек, а держать один общий монорепозиторий - с подпапкой на каждый стек внутри. Так все compose-файлы видны Komodo сразу через один Repo-ресурс, и не приходится плодить десятки отдельных записей.

.env-файлы в таком репозитории можно коммитить как есть, но обязательно делайте репозиторий приватным - иначе секреты (пароли БД, токены) окажутся доступны кому угодно, у кого есть ссылка на репозиторий.

Создаём Stack
#

  1. Resources → Stacks → Create Stack.
  2. Даём стеку имя (например, traefik, если это стек с реверс-прокси, или nginx-test для тестового примера).
  3. Server - на каком хосте (Periphery-агенте) разворачивать стек. Если у вас пока один хост - выбирать особо не из чего, но при добавлении второго хоста через отдельный Periphery-агент это поле определяет, куда именно уедет деплой.
  4. Select Repo - здесь либо выбираем ранее созданный Repo-ресурс, либо (если отдельный Repo-ресурс не заводили) настраиваем источник прямо тут же: Git ProviderAccountRepo (формат тот же - владелец/репозиторий, например stilicho/youtube) → Branch. Оба пути равнозначны, просто отдельный Repo-ресурс удобнее переиспользовать в Procedure, как показано ниже.
  5. Run Directory - путь до подпапки с compose-файлом внутри репозитория, например traefik, относительно корня репозитория.
  6. File Paths - имя самого compose-файла, которое будет подставлено в docker compose -f, относительно Run Directory. Если оставить пустым, Komodo подставит compose.yaml - если ваш файл называется иначе (например, привычнее многим docker-compose.yml), это обязательно нужно прописать явно в этом поле, иначе деплой упадёт на шаге валидации с ошибкой вида Missing files: compose.yaml, даже если сам файл на месте, просто под другим именем.
  7. Сохраняем и жмём Deploy - Komodo должна поднять контейнер(ы) из вашего compose-файла.
Note

Там же, чуть ниже, есть поле Clone Path - по умолчанию Komodo клонирует репозиторий в $root_directory/stacks/<имя стека> (в нашем случае это будет /home/stilicho/docker/stacks/traefik), и в 99% случаев трогать это поле не нужно. Полезно просто знать, что оно означает - именно этот путь вы увидите в логе шага Clone Repo, если понадобится туда зайти руками и что-то проверить.

Warning

Run Directory и File Paths - это два независимых поля, и обе ошибки выглядят очень похоже в логе (ERROR: A file doesn't exist after writing stack / Missing files: ...), но чинятся по-разному:

  • если репозиторий склонировался, но нужной подпапки внутри него как будто нет - проверяйте Run Directory;
  • если подпапка верная, но написано, что не хватает конкретно compose.yaml - проверяйте File Paths: скорее всего, ваш файл называется docker-compose.yml или как-то ещё, и нужно указать это имя явно, а не полагаться на дефолт.

Проверить результат можно двумя способами: посмотреть логи деплоя прямо на странице Stack-ресурса в Komodo (там видно каждый шаг - Clone Repo, Latest Commit, Validate Files, Deploy Compose - и на каком именно из них что-то пошло не так), либо зайти на хост и выполнить docker ps - контейнер должен появиться в списке с именем, соответствующим сервисам из compose-файла.

Если контейнер запустился - связка репозиторий → Komodo → Docker уже работает вручную. Осталось убрать последний ручной шаг - нажатие кнопки Deploy.

Шаг 4. Автодеплой по push
#

Здесь и происходит основная идея GitOps: вместо того чтобы каждый раз заходить в Komodo и жать Deploy, мы хотим, чтобы это происходило само при пуше в репозиторий.

Procedure в Komodo
#

Procedure - это последовательность из одного или нескольких шагов (Pull, Deploy, отправка уведомления и т.д.), которую можно запустить одной командой или по триггеру, например по webhook.

  1. Resources → Procedures → Create Procedure.
  2. Даём процедуре имя, например deploy-on-push.
  3. Добавляем шаги по порядку (кнопка Add Step, либо Add Stage в зависимости от версии интерфейса - шаги внутри Procedure выполняются строго сверху вниз):
    • Pull Repo - указываем тот же Repo-ресурс, что и в Stack. Этот шаг подтягивает последний коммит из ветки main в локальную рабочую копию на хосте.
    • Deploy Stack (если стек один) либо Batch Deploy Stack If Changed (если стеков в репозитории несколько) - применяет то, что подтянул предыдущий шаг.
  4. Сохраняем Procedure.
Tip

Вариант “If Changed” полезен, если в вашем монорепозитории лежит сразу несколько стеков: Komodo сравнивает содержимое папки каждого стека до и после Pull и передеплоивает только те, чьи файлы реально изменились в последнем коммите, а не пересоздаёт все контейнеры разом при любом чихе в репозитории.

Если стек в репозитории всего один, Procedure вообще не обязательна: у каждого Stack-ресурса на его собственной странице тоже есть готовый раздел Webhooks с URL вида .../listener/github/stack/<id>/deploy - можно навесить webhook сразу на него, без промежуточной Procedure. Сама Deploy-операция стека уже включает в себя актуализацию из git, отдельный шаг Pull для неё не нужен. Procedure с явными Pull + Batch Deploy имеет смысл именно тогда, когда одним пушем в монорепозиторий нужно затронуть несколько разных стеков одновременно.

Webhook в Forgejo
#

Формат webhook-адреса в Komodo фиксированный:

https://<HOST>/listener/<AUTH_TYPE>/<RESOURCE_TYPE>/<ID_OR_NAME>/<EXECUTION>

Для Forgejo/Gitea AUTH_TYPE - всегда github (Komodo проверяет подпись X-Hub-Signature-256, тот же формат, что использует GitHub, и Forgejo умеет отправлять webhook именно в нём). Для ресурса типа Procedure EXECUTION - это не команда вроде “deploy”, а имя ветки, на пуш в которую должна реагировать процедура (или __ANY__, чтобы реагировать на пуш в любую ветку); для Stack это как раз конкретная команда - /deploy или /refresh.

  1. Открываем страницу Procedure deploy-on-push (или страницу конкретного Stack, если решили обойтись без Procedure) в Komodo - в разделе Webhooks там уже будет готовый URL, собирать вручную ничего не придётся. Для Procedure это будет выглядеть так:

    https://komodo.stilicho.ru/listener/github/procedure/deploy-on-push/main
  2. В Forgejo идём в нужный репозиторий → Settings → Webhooks → Add Webhook → Forgejo (Forgejo поддерживает свой нативный формат webhook, отдельный от универсального Gitea-совместимого - выбирайте именно Forgejo, если он есть в списке; если нет, подойдёт и Gitea - формат событий совместим и с тем, и с другим).

  3. Заполняем форму:

    • Target URL - вставляем собранный URL из шага 1;
    • HTTP Method - POST;
    • POST Content Type - application/json;
    • Secret - по умолчанию вставляем значение KOMODO_WEBHOOK_SECRET, которое вы задали в compose-файле Komodo на шаге 2 (это глобальный секрет, общий для всех webhook на все ресурсы инстанса). У каждого ресурса в разделе Webhooks есть поле Webhook Secret, которым при желании можно задать свой отдельный секрет именно для него вместо глобального - но для домашней установки с одним пользователем в этом обычно нет необходимости;
    • Trigger On - выбираем событие Push Events (можно оставить только его - остальные события вроде issues или pull request’ов для нашей задачи не нужны).
  4. Сохраняем webhook. Forgejo сразу предложит отправить тестовый Test Delivery - стоит им воспользоваться: если Komodo ответит 200 OK, значит секрет и URL настроены верно, и можно переходить к проверке на реальном коммите.

Warning

Если в ответ на Test Delivery вместо кода ответа видите красный крестик и текст вида webhook can only call allowed HTTP servers ... deny '<домен>(<IP>:443)' - это не проблема с секретом или URL, это защита Forgejo от SSRF: по умолчанию она блокирует webhook на адреса из локальной сети. Убедитесь, что в переменной FORGEJO__webhook__ALLOWED_HOST_LIST контейнера Forgejo (см. compose-файл в Шаге 1) есть значение private - без него любой webhook на домен, который резолвится в 192.168.x.x/10.x.x.x, будет отклонён именно с такой ошибкой, сколько бы раз вы ни проверяли секрет и URL.

Note

Если в имени вашей Procedure есть пробелы или отличающийся от ID регистр, надёжнее подставить в URL числовой ID ресурса вместо имени - его можно скопировать со страницы ресурса в Komodo. Имя может со временем поменяться (вы захотите переименовать процедуру), а вот ID - никогда, так что ссылка в настройках webhook Forgejo не “отвяжется” от ресурса при переименовании.

Проверка
#

Все команды git ниже выполняются на вашем компьютере, в локальной копии репозитория - не на сервере, где стоят Forgejo и Komodo. Сервер только принимает push и реагирует на него через webhook.

Если репозитория ещё нет локально, клонируем его (URL - тот же, что использовали при создании Repo-ресурса в Komodo; при первом обращении git спросит логин и пароль - в качестве пароля используем тот же токен, что регистрировали в Komodo, либо заводим для себя отдельный личный токен с правами на запись):

git clone http://<адрес-хоста>:3000/stilicho/youtube.git
cd youtube

Дальше меняем что-нибудь в нужном compose-файле - например, версию образа - и пушим:

git add .
git commit -m "bump nginx version"
git push
Tip

Изменить файл можно и прямо в веб-интерфейсе Forgejo (открыть файл → карандаш Edit → внизу страницы Commit Changes) - тогда локальный клон вообще не нужен, коммит и push произойдут на стороне сервера автоматически. Для быстрых правок это даже удобнее, чем возиться с git-клиентом на своей машине.

После git push события развиваются так:

 sequenceDiagram

```
participant Вы
participant Forgejo
participant Komodo
participant Docker

Вы->>Forgejo: git push
Forgejo->>Komodo: webhook (push event)
Komodo->>Komodo: Pull Repo
Komodo->>Docker: Deploy Stack
Docker-->>Komodo: контейнер обновлён
```

Через несколько секунд в разделе Procedures → deploy-on-push в Komodo должно появиться новое выполнение (run) со статусом Success - открыв его, можно увидеть подробный лог обоих шагов: что именно подтянул Pull Repo и что вывел Deploy Stack при пересоздании контейнера. Контейнер при этом пересоздаётся с новым конфигом без единого ручного действия с вашей стороны.

Если выполнение не запустилось автоматически - первым делом стоит проверить лог доставки webhook на стороне Forgejo (Settings → Webhooks → <ваш webhook> → Recent Deliveries): там видно, каким статус-кодом ответила Komodo, и это обычно сразу указывает на причину (неверный секрет - 401, неверный URL - 404 или таймаут соединения).

Проверяем откат
#

Ради интереса стоит сразу опробовать и обратную операцию - снова в той же локальной копии репозитория (cd youtube, если ещё не там):

git revert HEAD
git push

git revert HEAD создаёт новый коммит, который отменяет изменения последнего коммита (в нашем случае - той самой правки версии образа), не переписывая при этом историю - в отличие от git reset, старый коммит остаётся в логе, просто поверх него добавляется отменяющий. Это важно: если между вашей правкой и откатом кто-то (или вы сами) успеет запушить ещё один коммит, HEAD уже будет указывать не на ту правку, которую вы хотели откатить - в такой ситуации точнее откатывать конкретный коммит по хэшу: git revert <hash> вместо git revert HEAD.

После git push тот же webhook сработает снова, Komodo подтянет откаченный коммит и вернёт предыдущую версию стека - контейнер пересоздастся уже со старой версией образа. Это и есть главное практическое преимущество подхода: ошибка исправляется так же просто, как и вносится, двумя командами в терминале, без необходимости заходить на сервер и разбираться, что именно там сейчас развёрнуто.

Что дальше
#

Показанная здесь схема - минимальная: один хост, MongoDB и SQLite внутри тех же контейнеров, один пользователь-администратор. Когда захочется расти, есть куда:

  • вынести базы данных Forgejo и Komodo на внешний PostgreSQL/MongoDB - полезно, если на хосте уже есть общая СУБД для нескольких сервисов;
  • подключить несколько хостов через отдельные Periphery-агенты - Core остаётся один, а Periphery ставится на каждый дополнительный хост и указывает на свой PERIPHERY_ROOT_DIRECTORY;
  • добавить CI через Forgejo Actions - например, автоматическую проверку синтаксиса compose-файлов (docker compose config) перед тем, как коммит вообще попадёт в основную ветку;
  • настроить оповещения о деплоях - Komodo умеет отправлять уведомления в Discord, Slack, Telegram или на произвольный webhook при завершении Procedure;
  • по мере роста числа репозиториев переходить на Specific repositories-токены (как для Forgejo v15 в этой статье) везде, где раньше стоял токен с доступом “ко всему” - так компрометация одного токена не откроет доступ сразу ко всем вашим репозиториям.

Но для первого знакомства с GitOps в homelab хватит и того, что уже есть: репозиторий как источник правды, автоматический деплой по push и откат в одну команду.

Self-Hosting - This article is part of a series.
Part : This Article

Related

Переезжаем с Portainer на Komodo: полная установка и настройка

··5397 слов·26 минут· loading · loading
Пошаговая инструкция по установке Komodo как альтернативы Portainer: docker-compose, MongoDB, bind mount вместо volumes, вход через Authentik (OIDC) и подключение дополнительных хостов через Periphery.

Gotify: свой собственный сервер push-уведомлений для гипервизора и не только

·2133 слов·11 минут· loading · loading
Устанавливаю Gotify в Docker и настраиваю push-уведомления от Proxmox VE, CrowdSec и Komodo - отдельный канал для инфраструктурных событий, без единого сообщения в общем чате Telegram.