Forgejo. Миграции, реестр пакетов, CI/CD
Переносим проекты из внешних систем, поднимаем свой реестр Docker образов и настраиваем CI/CD внутри одного LXC
В предыдущей статье мы запустили сервер Forgejo в LXC Proxmox и остановились на странице первоначальной настройки.
Большинство настроек оставляем по умолчанию, включая использование однофайловой субд SQLite - для небольшой команды или homelab вполне достаточно. Заполняем параметры учётной записи администратора и создаём сервер.
Особенности
Рассматривать стандартные для системы контроля версий сценарии работы я не стану - wiki, ветки, тэги, issues, pull requests, работа в команде, права доступа и т.д. - всё это понятно любому пользователю, который на постоянной основе с подобными решениями взаимодействует.
Отмечу то, что бросается в глаза сразу после начала работы:
- Быстрый и предсказуемый интерфейс, который не пестрит и не отвлекает.
- Отсутствие рекламных блоков, опций с намёком на социальную сеть (не считая follow и star) и AI фичей.
- Базовые по функциональности проекты, issues, поиск по коду - заметно проще по сравнению с GitHub или GitLab.
- Отсутствие встроенных сканеров безопасности - придётся подключать что-то извне.
Далее пройдёмся по некоторым практическим фичам.
Миграция репозитория
Для быстрого переноса своих проектов из GitHub / GitLab и других аналогичных сервисов в Forgejo предусмотрели миграции.
Создать... -> Выполнить перенос

Для примера клонировал свой публичный проект website-builder-template с GitHub. Все ветки, коммиты и прочие параметры были перенесены, репозиторий готов к работе локально. Для миграции приватного хранилища требуется токен доступа.

Pull-зеркало
Сценарий использования: Основная работа ведётся, например, на GitHub/GitLab, а Forgejo выступает как локальный бэкап или точка входа для вашей внутренней инфраструктуры (CI, Registry, локальные скрипты).
Как включить
Среди параметров миграции есть чекбокс “Этот репозиторий будет зеркалом” - это вариант настройки pull-зеркала, т.е. ваша локальная версия будет периодически синхронизироваться с внешним репозиторием и подтягивать все изменения.
Push-зеркало
Сценарий использования: Вы работаете в Forgejo, но хотите, чтобы копия репозитория автоматически отправлялась на GitHub / GitLab.
Важно! Это способ односторонней синхронизации локального хранилища с внешним. Если кто-то сделает коммит напрямую во внешний репозиторий, а не в Forgejo, возникнет рассинхронизация. Forgejo не сможет смержить конфликты автоматически и зеркало “упадёт” со статусом ошибки.
Как включить
- Создаём Personal Access Token с доступом к репозиторию на внешнем ресурсе.
- Возвращаемся в репу в Forgejo:
Настройки -> Репозиторий -> Зеркалирование - Указываем ссылку на внешнюю репу с добавлением секции авторизации.
1 2
# GitHub https://oauth2:GITHUB_TOKEN@github.com/username/repo.git - Задаём интервал.
- “Добавить push-зеркало”.
Встроенный Package Registry
Не нужно поднимать отдельные машины для хранения Docker образов, Go модулей или Python пакетов, т.к. в Forgejo уже встроен реестр с поддержкой множества популярных форматов, включая Docker, npm, NuGet, Maven, Conda, PyPI, Go, Helm и другие.
Настройка собственного Docker-хаба
1) Создаём токен доступа для приложений с правами на чтение и запись пакетов.
Настройки -> Приложения -> Новый токен доступа

2) Если Forgejo работает по http, то нужно прописать адрес сервера в разрешенные реестры Docker Engine. Для https пропускаем.
1
2
3
4
5
6
7
8
9
10
11
12
13
# на ubuntu/debian с установленным Docker
# редактируем или создаём файл daemon.json
sudo vi /etc/docker/daemon.json
# прописываем адрес forgejo
{
"insecure-registries": [
"10.60.60.101:3000"
]
}
# перезапускаем docker
sudo systemctl restart docker
3) Авторизуемся через Docker в Forgejo.
1
2
3
4
5
6
docker login 10.60.60.101:3000
Username: desoft # имя пользователя в Forgejo
Password: # созданный ранее токен
Login Succeeded
4) Собираем и публикуем образ
Клонируем репозиторий, для которого будем собирать образ
1
2
git clone http://10.60.60.101:3000/desoft/website-builder-template-local.git
cd website-builder-template-local
Добавляем Dockerfile и тестовую html страницу
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Dockerfile
vi Dockerfile
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html
EXPOSE 80
# html
vi index.html
<!DOCTYPE html>
<html>
<head><title>Forgejo Docker Registry Test</title></head>
<body>
<h1>Hello from Forgejo Docker Registry!</h1>
<p>Образ собран и запушен в registry.</p>
</body>
</html>
Сборка и публикация
1
2
3
4
5
6
# сборка с правильным тегом
# формат: 10.60.60.101:3000/OWNER/REPO_NAME:TAG
docker build -t 10.60.60.101:3000/desoft/website-builder-template-local:1.0.0 .
# публикация
docker push 10.60.60.101:3000/desoft/website-builder-template-local:1.0.0
- Docker упаковывает образ
- Отправляет его в Forgejo
- Forgejo сохраняет образ в хранилище (по умолчанию в локальную файловую систему, но можно подключить и s3, если планируется большой объём артефактов).
- Для очистки устаревших пакетов и сбора мусора как реестров так и самих репозиториев в административных настройках существуют заготовленные автозадачи, которые могут быть исполнены по расписанию или вручную.
Загрузка образа
1
2
3
4
# не забываем предварительно подключиться к реестру
# через docker login, если новая машина
docker pull 10.60.60.101:3000/desoft/website-builder-template-local:1.0.0
CI/CD на Forgejo Actions
На данном этапе у нас имеются репозиторий с кодом и собственный реестр Docker образов. Осталось связать все этапы работы над проектом автоматическим конвейером, чтобы обойтись без ручной сборки и публикации.
push в репозиторий → сборка образа → публикация в Registry
Forgejo Actions
Forgejo Actions — это система непрерывной интеграции, совместимая с GitHub Actions по синтаксису YAML.
Особенности:
- Runner внутри LXC с Forgejo - отдельная виртуалка не требуется (runner можно и следует разместить на отдельной машине в случае работы даже небольшой команды, но для тестов / редких запусков хватит и такого исполнения).
- Сборка прямо в контейнере - локально, безопасно, но надо учитывать повышение нагрузки в связи с этим.
- Секреты и переменные хранятся в настройках репы / организации.
- Кэш хранится локально. Автоматической очистки не происходит и задач под это еще добавлено не было, поэтому нужно держать на контроле и периодически чистить cache-каталог (обычно $HOME/.cache/actcache).
Устанавливаем Runner
Поскольку Forgejo у меня крутится в Docker-контейнере внутри LXC, то runner я размещу рядом в отдельном Docker-контейнере.
- Готовим
compose.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
networks:
forgejo:
external: false
services:
forgejo:
image: codeberg.org/forgejo/forgejo:15
container_name: forgejo
environment:
- USER_UID=1000
- USER_GID=1000
restart: unless-stopped
networks:
- forgejo
volumes:
- ./forgejo/data:/data
- /etc/localtime:/etc/localtime:ro
ports:
- '3000:3000'
- '222:22'
dind:
image: docker:dind
container_name: forgejo-dind
privileged: true
command: ['dockerd', '-H', 'tcp://0.0.0.0:2375', '--tls=false']
networks:
- forgejo
volumes:
- dind-data:/var/lib/docker
# если forgejo работает по http - добавляем конфигурацию
- ./dind-daemon.json:/etc/docker/daemon.json:ro
ports:
- '2375:2375'
runner:
image: data.forgejo.org/forgejo/runner:4.0.0
container_name: forgejo-runner
depends_on:
- dind
- forgejo
environment:
DOCKER_HOST: tcp://10.60.60.101:2375
networks:
- forgejo
volumes:
- ./runner-data:/data
command: '/bin/sh -c "while :; do sleep 1; done"'
volumes:
dind-data:
forgejo — сам Git-сервер с веб-интерфейсом, реестром пакетов и системой управления репозиториями.
dind — отдельный Docker-демон внутри контейнера, который предоставляет runner’у API для сборки и пуша образов в изолированной среде.
runner — агент Forgejo Actions, который получает задачи от сервера и выполняет CI-пайплайны, обращаясь к dind для Docker-операций.
- Создаём новый глобальный runner в веб интерфейсе Forgejo
Где: Панель управления -> Действия -> Исполнители -> Добавить нового исполнителя
Имя: runner-lxc
Описание: оставить пустым
После создания видим UUID и токен runner’а, однако с этим токеном зарегистрировать runner не получилось. На той же странице Исполнители есть вкладка Показать токен регистрации, вот его и запоминаем.

- Стартуем контейнеры и регистрируем runner
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# создаём директорию для данных runner
mkdir runner-data
chown -R 1000:1000 ./runner-data
# если forgejo работает по http, то для dind надо разрешить http реестр
# т.к. по умолчанию он требует https
vi dind-daemon.json
{
"insecure-registries": ["10.60.60.101:3000"]
}
# запускаем контейнеры
docker compose up -d
# заходим в runner и регистрируем
docker exec -it forgejo-runner /bin/sh
forgejo-runner register
# Instance URL: http://10.60.60.101:3000
# Token: токен регистрации, который сохранили выше
# Name: runner-lxc
# Labels: оставить пустым
INFO Registering runner, name=runner-lxc, instance=http://10.60.60.101:3000, labels=[docker:docker://node:20-bullseye].
DEBU Successfully pinged the Forgejo instance server
INFO Runner registered successfully.
exit
# останавливаем контейнер runner'а
docker compose down runner
# меняем в compose.yaml строку command на
# command: '/bin/sh -c "sleep 5; forgejo-runner daemon"'
# запускаем контейнер runner'а
docker compose up -d runner
После этого в веб интерфейсе Forgejo среди исполнителей появится runner со статусом Простаивает (Idle).
Добавляем секреты
Pipeline будет пушить образ в Registry от имени Forgejo. Для этого нужен токен приложения, который мы создавали в предыдущем разделе для docker login.
- Идём в настройки репы:
Настройки -> Действия -> Секреты -> Добавить секрет. - Заполняем и сохраняем.
- Название:
REGISTRY_TOKEN - Значение: токен приложения
- Название:
Для удобства можно добавить переменную с адресом реестра.
Настройки -> Действия -> Переменные -> Добавить переменную- Заполняем и сохраняем.
- Название:
REGISTRY_URL - Значение: адрес реестра (в моём случае
10.60.60.101:3000).
- Название:
Готовим структуру workflow
В корне репозитория создаём директорию .forgejo
1
2
3
4
5
6
7
repo/
├── .forgejo/
│ └── workflows/
│ └── build.yml
├── Dockerfile
├── index.html
└── ...
Пишем workflow
Создаём файл .forgejo/workflows/build.yml в репозитории следующего содержания:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
name: Build and Push Docker Image
on:
push:
branches: [main]
tags: ['v*']
jobs:
build:
runs-on: docker
env:
DOCKER_HOST: tcp://10.60.60.101:2375
steps:
- uses: actions/checkout@v4
- name: Install Docker CLI
run: |
apt-get update && apt-get install -y docker.io
- name: Login to Registry
run: |
echo "$" | docker login $ -u $ --password-stdin
- name: Build and push
run: |
TAG=${GITHUB_REF_NAME:-latest}
docker build -t $/$:${TAG} .
docker push $/$:${TAG}
Что тут происходит:
| Шаг | Действие | Что происходит |
|---|---|---|
on: push: branches: [main] / tags: ['v*'] |
Триггер | Pipeline запускается при push в ветку main или при пуше git-тега v* |
runs-on: docker |
Выбор runner | Job выполняется внутри Docker-контейнера node:20-bullseye через наш Forgejo Runner |
env: DOCKER_HOST: tcp://dind:2375 |
Переменная окружения | Все docker-команды внутри job обращаются к демону dind по TCP вместо локального сокета |
actions/checkout@v4 |
Клонирование кода | Скачивает содержимое репозитория во временную рабочую директорию внутри job-контейнера |
Install Docker CLI |
Установка пакета | Внутри node:20-bullseye ставится docker.io, чтобы появилась команда docker |
Login to Registry |
Авторизация | Docker CLI логинится в ваш Forgejo Registry используя токен из Secrets |
Build and push |
Сборка и публикация | Определяется тег (имя ветки или тег git), собирается образ из Dockerfile, пушится в Registry |
github.repository— автоматически подставитOWNER/REPO_NAME.github.actor— логин пользователя, который запушил.- Для тегов
v1.0.0образ получит тег1.0.0.
Запускаем и проверяем
Ранее мы в репу уже добавили Dockerfile и index.html для тестирования сборки образа. Сейчас коммитим и пушим файл workflow.
1
2
3
git add .forgejo/workflows/build.yml
git commit -m "ci: add docker build pipeline"
git push origin main
- Forgejo ловит событие push.
- Создаёт задачу (job) во вкладке Действия (Actions).
- Внутри LXC поднимается временный контейнер (runner).
- Выполняются шаги по порядку.
✅ Пакет собран и опубликован в реестре репозитория.
P. S. Часто вставляем ip адрес самого сервера forgejo в конфигурации, workflow, compose. Переход на https частично решает этот вопрос, а также вынос REGISTRY_URL в переменные окружения системы, а не только репозитория.
Таким образом, мы перенесли репозиторий с GitHub в собственный Forgejo, рассмотрели способы зеркалирования, развернули встроенный Package Registry и опубликовали в него первый Docker-образ, а затем связали всё в единый поток: теперь каждый push в main автоматически собирает образ и отправляет его в наш приватный реестр - все на одном LXC в Proxmox и под полным контролем.
В следующей части CI/CD рассмотрим еще глубже, runner’у выделим отдельный сервер, настроим работу с бэкапами и подключим внешние уведомления.





