Поднимаем Git-сервер с CI на своём VPS: Docker Compose, раннер, HTTPS, переезд с GitHub и бэкап, восстановление которого мы проверили.
Forgejo на VPS — это ваш собственный Git-сервер: репозитории, заявки, запросы на слияние и встроенный CI, который запускает тесты при каждой отправке кода. Ниже вся цепочка от пустого сервера до работающей сборки: Docker Compose, веб-установщик, SSH-ключ, раннер, HTTPS через Caddy, переезд репозитория с GitHub и бэкап, который мы не только сделали, но и восстановили на втором экземпляре. Основную цепочку (установка, ключ, push, раннер, CI, бэкап и восстановление) мы прогнали 2026-11-03 команда за командой. Не прогоняли ssh -L для тоннеля, ssh-keygen, запуск Caddy на портах 80 и 443 и sed с https-адресом (там мы проверили близкую форму). Всё адаптированное и непроверенное помечено в тексте и в отдельном списке в конце.
Коротко. Положите в папку
docker-compose.ymlс образом Forgejo (веб только на 127.0.0.1, SSH на порту 222), запуститеdocker compose up -d, откройте веб-установщик и поставьте SSH-порт 222. Добавьте SSH-ключ в профиль и отправьте первый push. Для CI сгенерируйте общий секрет, зарегистрируйте раннер командойforgejo-cli actions registerи добавьте его сервис в compose. Бэкап делаетforgejo dump, но хост-ключи SSH в него не входят, а базу при восстановлении надо собирать изforgejo-db.sql, а не из файлаgitea.dbв архиве.
Свой сервер имеет смысл, когда код и сборку вы хотите держать у себя: приватные репозитории без оглядки на чужие условия, сборка на машине, которую вы контролируете, и возможность в любой момент всё забрать и уехать. Это работа на вас: обновления, бэкапы и безопасность теперь ваши. Если вам достаточно размещённого сервиса и возиться с этим не хочется, оставайтесь там. Если же вы разработчик, небольшая команда или агентство, которому нужно хранить клиентский код у себя, — читайте дальше.
Forgejo — программа для самостоятельного размещения Git-репозиториев: веб-интерфейс, пользователи, заявки, запросы на слияние. Это ответвление (форк) Gitea, следы заметны в строке версии (17.0.1+gitea-1.22.0) и в путях внутри образа. Forgejo Actions — встроенный CI: вы кладёте в репозиторий файл со сценарием (workflow), и при каждом push раннер запускает его в контейнере. Формат близок к GitHub Actions, но не совпадает с ним: часть чужих сценариев придётся поправить. Если вы ищете альтернативу GitHub, которую можно целиком поставить на свой сервер, это один из рабочих вариантов.
В форме заказа HIP также есть готовое приложение Forgejo. Здесь же мы ставим всё вручную, чтобы вы видели каждый шаг и сами выбирали версию.
К концу у вас будет Forgejo с SQLite внутри, раннер, который собирает проект в контейнере, и Caddy с HTTPS перед ними. Вот что это стоит по ресурсам. Цифры снял docker stats в простое и docker images на нашем стенде, у вас они чуть разойдутся.
Компонент | Размер образа | ОЗУ в простое |
|---|---|---|
Forgejo 17.0.1 | 291 МБ | 100-220 МиБ (замеры 116, 137, 165 и 217 МиБ в разные моменты) |
Раннер v13.2.1 | 66,8 МБ | около 9-15 МиБ |
Образ для заданий node:22-bookworm | 1,64 ГБ (скачивается при первом задании) | около 3 МиБ на контейнер с простой командой |
Caddy (шаг 5) | не измеряли | около 18 МиБ |
В простое Forgejo и раннер вместе занимали у нас 150-230 МиБ (Forgejo 137 или 217 МиБ, раннер 9-13 МиБ). Пик суммы по всем контейнерам стенда во время CI-задания был 328 МиБ, но в сумму входили и второй экземпляр Forgejo из проверки восстановления, и Caddy, так что сам стек укладывается примерно в 230 МиБ. С запасом на систему и тяжёлые сборки закладывайте не меньше 512 МиБ свободной ОЗУ под этот стек (это наш совет, не замер). Данные после установки, двух репозиториев и трёх запусков CI заняли 29 МБ, папка раннера — 628 КБ. Вывод для выбора тарифа — наш, это не бенчмарк: для личного репозитория или небольшой команды хватит 1 vCPU и 2 ГБ ОЗУ, а для тяжёлых сборок берите запас по ОЗУ и по диску. Образ для заданий один занимает 1,64 ГБ, поэтому заодно загляните в статью о том, как чистить диск от Docker.
Понадобится:
docker compose version должна отвечать (у нас Docker 29.1.3 и Compose v2.40.3). Если Compose для вас в новинку, начните со статьи про несколько сервисов в Docker Compose.Как мы проверяли: стенд — отдельный демон Docker с чистым хранилищем на VPS HIP-HOSTING, поэтому боевые контейнеры не пострадали. В выводах путь к рабочей папке заменён на ~/. Команды docker написаны без sudo: считаем, что вы root или состоите в группе docker.
Создайте папку forgejo-stack, внутри неё файл docker-compose.yml с таким содержимым. Рабочая папка важна: от её имени Compose назовёт сеть (у нас получилась forgejo-stack_forgejo), и это имя пригодится на шаге 4.
Разберём строки, которые читаются не с первого взгляда.
image: ...forgejo:17 — образ с тегом 17, то есть ветка 17.x. У нас под ним оказалась версия 17.0.1. Зеркало data.forgejo.org/forgejo/forgejo:17 тянется точно так же.USER_UID=1000 и USER_GID=1000 — числовые номера пользователя и группы (UID и GID — так Linux различает владельцев файлов), под которыми Forgejo работает внутри контейнера. Файлы в ./forgejo будут принадлежать именно им, поэтому папка должна принадлежать тем же 1000:1000 (следующая команда).restart: always — контейнер поднимется сам после падения и после перезагрузки сервера../forgejo:/data — всё, что надо сохранить (репозитории, база, конфиг), лежит в обычной папке рядом с compose. Контейнер можно пересоздать, данные останутся.127.0.0.1:3000:3000 — веб-интерфейс слушает только сам сервер. Снаружи его не видно, и это сознательно: пока перед ним нет HTTPS, форму входа в интернет выставлять нельзя. Потом перед ним встанет Caddy.222:22 — SSH Forgejo внутри контейнера слушает 22, а наружу мы отдаём его на 222: порт 22 уже занят системным SSH, через который вы заходите на сам сервер. Если оставить так, как написано, порт слушается на всех адресах сервера (разбор на шаге 5).Создаём папку данных с нужным владельцем и запускаем. Без chown у Forgejo не будет прав писать в папку, которую до этого создали вы.
Docker скачает образ (291 МБ). Сразу после запуска веб отвечает кодом HTTP 200. Проверим, что контейнер жив и порты такие, как мы хотели:
Здесь важна колонка PORTS: веб привязан к 127.0.0.1, а 222 — к 0.0.0.0 и [::], то есть он виден всему интернету, если ему не мешает файрвол. К этому вернёмся. Время в колонке STATUS у вас будет своим.
Веб ещё закрыт для интернета, поэтому на этом этапе заходим через SSH-тоннель: это когда порт сервера пробрасывается на ваш компьютер по SSH-соединению. С ноутбука выполните ssh -L 13000:127.0.0.1:3000 user@IP-сервера и откройте в браузере http://127.0.0.1:13000. Подробно про тоннели — в статье про проброс портов по SSH. Поэтому на всех скриншотах ниже в адресе стоит 127.0.0.1:13000, а не домен.
Откроется страница «Initial configuration». Большинство полей трогать не надо: значения по умолчанию подходят.
Поле | Что делать | Почему |
|---|---|---|
Database type / Path | оставить SQLite3 и | Проверяли только этот вариант. PostgreSQL и MySQL мы не пробовали |
Repository root path, User to run as | оставить | Совпадает с устройством образа |
Server domain | домен или адрес сервера (на стенде | Попадёт в адреса клонирования |
SSH server port | поставить 222 | Это порт, который увидят клиенты. Внутри контейнера SSH по-прежнему слушает 22 |
HTTP listen port | оставить 3000 | Совпадает с портом в compose |
Base URL | адрес, по которому вы открываете сайт (на стенде | Позже заменим на https-адрес домена (шаг 5) |
Disable self-registration | оставить включённой | Иначе любой, кто найдёт адрес, заведёт аккаунт. При работающем раннере это особенно неприятно (см. безопасность раннера) |
Enable update checker | включена по умолчанию, мы не трогали | Раз в какое-то время смотрит TXT-запись DNS release.forgejo.org и сообщает о новой версии |
Administrator account settings | раскрыть и создать администратора | Блок свёрнут по умолчанию. Без него вы потом не войдёте |
Установщик с заполненными полями. Адрес в браузере выше кадра: 127.0.0.1:13000 через SSH-тоннель.
Мы создали администратора fjadmin с адресом [email protected]; у вас должны быть настоящий адрес и длинный пароль. Нажмите «Install Forgejo» и подождите: у нас до панели прошло около 20 секунд. После установки в forgejo/gitea/conf/app.ini появляются ROOT_URL, SSH_DOMAIN, SSH_PORT = 222 и INSTALL_LOCK = true, этот файл нам ещё понадобится.
Панель после установки. Внизу страницы видна версия: 17.0.1.
Версию можно спросить и через API, пригодится после обновлений. Работает только после установки: до неё API отвечает Not Found.
Ключ нужен, чтобы отправлять код без пароля: у вас остаётся закрытая половина, а в Forgejo вы кладёте открытую. Если пары ключей нет, создайте её командой ssh-keygen -t ed25519 и задайте парольную фразу. Открытый ключ — это содержимое файла с расширением .pub. Добавьте его в профиль: Settings → SSH / GPG keys → Add key.
Раздел SSH / GPG keys. Ключ lab добавлен, Forgejo показывает его отпечаток и дату последнего использования.
Теперь проверьте, что сервер вас узнаёт. Мы запускали команды на самом сервере, поэтому адрес 127.0.0.1; с вашего компьютера подставьте адрес сервера или домен, но тогда порт 222 должен быть до него доступен (см. шаг 5). При первом подключении SSH спросит, доверять ли ключу сервера, — это нормально.
-T — не открывать терминал: Forgejo всё равно не даёт оболочку.-p 222 — порт, который мы отдали наружу.git, это не ваше имя: Forgejo различает людей по ключу.Ответ «Hi there, ...» — успех. Дальше создайте пустой репозиторий demo без README (кнопка «+» в шапке → New repository; мы создавали через API) и отправьте в него код. Репозиторий заводите в отдельной папке, например ~/demo, а не в forgejo-stack: иначе README и коммиты попадут в папку самого стека. Команды docker compose выполняйте из forgejo-stack.
Среди строк вывода ищите * [new branch] main -> main. Порт 222 в адресе обязателен: без него git пойдёт на 22 и попадёт в системный SSH.
Forgejo сам сценарии не выполняет, это делает отдельная программа — раннер. Он подключается к Forgejo, забирает задания и запускает каждое в свежем контейнере. Нам нужно познакомить их друг с другом, и здесь легко заблудиться в устаревших инструкциях.
В старых статьях раннер регистрируют командой forgejo-runner register с токеном из веб-интерфейса. В документации Forgejo команда forgejo-runner register помечена как устаревшая (deprecated). Мы на v13.2.1 пошли путём с общим секретом, как в официальном примере compose: вы придумываете общий секрет, говорите Forgejo «такой раннер существует», а раннеру вписываете тот же секрет в конфиг. Токен регистрации из веб-интерфейса в роли секрета не подходит, это прямо сказано в том же примере. Секрет — 40 шестнадцатеричных символов, а UUID раннера строится из первых 16 символов этого секрета.
Выполняйте всё в одном окне терминала: переменные SECRET и UUID нужны и здесь, и в конфиге ниже.
openssl rand -hex 20 — 20 случайных байт в виде 40 шестнадцатеричных символов.-u git — команда выполняется от пользователя git, под которым живёт Forgejo.--name lab-runner — имя раннера в списке; --keep-labels — при повторной регистрации не затирать метки, уже записанные в Forgejo; сами метки задаются в конфиге раннера (ниже).Вывод последней команды мы не сохраняли; проверка успеха будет в админке и в логе раннера ниже. Секрет нигде не публикуйте.
Теперь файл настроек раннера. Создайте runner/config.yml в любом редакторе с содержимым ниже. В карточке значения uuid и token скрыты звёздочками: впишите туда то, что напечатают echo "$UUID" и echo "$SECRET".
capacity: 1 — раннер берёт одно задание за раз; на небольшом сервере это защищает память.timeout: 3h — задание, которое идёт дольше трёх часов, будет остановлено.labels: docker:docker://... — метка docker и образ, в котором пойдут задания. В сценарии вы напишете runs-on: docker, и задание попадёт сюда. Образ скачается при первом запуске.container.network — сеть Compose, в которой живёт Forgejo. Без неё контейнер задания не увидит адрес http://forgejo:3000. Имя сети — это имя папки плюс _forgejo; на своём сервере проверьте через docker network ls.docker_host: "-" — не отдавать сокет Docker в контейнеры самих заданий (по документации Forgejo это значение по умолчанию). Нужен Docker внутри заданий — читайте раздел документации про доступ к Docker.$ sudo chown 1000:1000 runner/config.yml && sudo chmod 600 runner/config.ymlРаннеру нужен доступ к Docker сервера, иначе ему нечем запускать контейнеры заданий. Мы пробрасываем в него сокет Docker. Сначала узнайте номер группы, которой принадлежит сокет: пользователь раннера должен в неё входить.
У нас это 112, у вас может быть другое число. Допишите сервис runner в конец docker-compose.yml, подставив своё число в group_add. Вот файл целиком, как он выглядит после правки (первый сервис вы уже знаете):
Для раннера тот же принцип, что и у сервера: данные в ./runner, общая сеть, перезапуск при падении, depends_on запускает его после контейнера Forgejo (готовности Forgejo он не ждёт). Строка с сокетом пробрасывает Docker сервера внутрь контейнера раннера, а group_add даёт пользователю раннера право его читать. Запустим и посмотрим:
В логе раннера должно появиться, что он зарегистрирован: у нас это строка runner: lab-runner, with version: v13.2.1, with labels: [docker], ephemeral: false, declared successfully, а следом [poller] launched. В веб-интерфейсе раннер виден в админке (Admin settings → Actions → Runners): имя lab-runner, метка docker, тип Global, статус Idle.
Раннер зарегистрирован и ждёт заданий. Рядом есть кнопка «Create new runner»: этим путём мы не шли.
Сценарии лежат в репозитории в папке .forgejo/workflows. Положим туда самый простой: он забирает код и печатает версию Node, имя машины и список файлов. Так мы увидим, что вся цепочка (push, раннер, контейнер) работает.
on: [push] — запускать при каждой отправке коммитов.runs-on: docker — метка раннера из конфига.actions/checkout@v4 — готовый шаг, который скачивает код репозитория в контейнер. Forgejo берёт его с https://data.forgejo.org/actions/checkout.name: — подпись шага в интерфейсе. Без неё многострочный run: | Forgejo показывает в заголовке одной длинной строкой, поэтому давайте шагам имена.Закоммитьте файл и отправьте. Раннер подхватит задание сам, через несколько секунд оно появится в репозитории на вкладке Actions.
Эти строки — лог раннера, когда он уже принимал задания; номер задачи у вас будет свой. На нашем стенде первый запуск повис на «Set up job» минуты на две с половиной и упал, поэтому в списке на скриншоте красный запуск №1. Причина была в стенде, а не в Forgejo (подробнее ниже), после исправления остальные запуски прошли успешно, последний, №4, за 2 секунды: образ к тому времени уже лежал на диске. На самом первом запуске у вас добавятся минуты на загрузку 1,64 ГБ.
Список запусков: №1 упал на зависании сети стенда, №2-4 успешны.
Лог запуска №4: все шаги раскрыты, у каждого своё имя из name:.
В логе видно: v22.23.4 (версия Node в образе), строку hello from the runner on с именем контейнера и список файлов, среди которых .forgejo, .git и README.md (на скриншоте есть ещё restored.txt из проверки восстановления).
Раннер по определению выполняет произвольный код: всё, что написано в сценарии, он исполняет у вас на сервере. Это называют удалённым выполнением кода (RCE), и здесь это не уязвимость, а принцип работы. Сокет Docker смонтирован в контейнер раннера: он создаёт контейнеры заданий, то есть управляет демоном хоста. В нашем конфиге стоит docker_host: "-", и по документации Forgejo это значит, что в контейнеры самих заданий сокет не отдаётся (это значение по умолчанию). Режим automount, который отдаёт сокет заданиям, по документации «не даёт никакой изоляции»: из задания можно, например, смонтировать корень хоста. У нас он не включён. Но и без него раннер выполняет произвольный код (в документации сказано, что вся цель Forgejo Actions — удалённое выполнение кода), поэтому схема допустима только для доверенных пользователей и своих репозиториев. Не включайте открытую регистрацию и не пускайте на этот раннер чужие репозитории.
Вариант | Когда подходит | Статус у нас |
|---|---|---|
Сокет Docker хоста только в контейнере раннера, | Свой код и свои люди | Проверено, работает |
| Только если вы понимаете риск: по документации изоляции нет | Не включали |
Docker-in-Docker (DinD) | Когда нужна изоляция от демона хоста; описан в документации Forgejo (раздел про доступ к Docker) | Не проверяли |
Отдельная виртуальная машина или сервер под раннер | Когда в репозиториях может быть чужой код (документация советует изолировать раннер в своей виртуальной машине) | Не проверяли |
Скорее всего, контейнер задания не выходит в интернет и не может скачать образ или шаг. Проверьте это напрямую, без Forgejo:
$ docker run --rm alpine wget -qO- https://data.forgejo.orgЕсли команда зависла или вернула сетевую ошибку, дело в сети Docker или файрвола. У нас так было из-за стенда: политика пересылки в UFW и отдельный Docker без своих правил; на обычном сервере с Docker из пакета такого, как правило, не бывает, но отдельно мы это не проверяли. Вторая частая причина — забытый container.network в конфиге: тогда задание не видит http://forgejo:3000.
Caddy — веб-сервер, который умеет сам получать и продлевать сертификаты. Здесь он работает как обратный прокси: принимает HTTPS-запросы на вашем домене и передаёт их Forgejo. Установку Caddy на хост мы разбирали в статье про Caddy; тут запустим его в контейнере в той же сети, чтобы он видел Forgejo по имени.
Сначала домен: создайте A-запись на IP вашего сервера (как это делается, написано в справке про привязку домена). Затем Caddyfile из двух строк по существу:
git.example.com замените своим доменом. forgejo:3000 — имя контейнера и его порт внутри общей сети: поэтому порт веба на хосте нам по-прежнему не нужен, 127.0.0.1:3000 можно оставить как есть. Запускаем:
$ docker run -d --name caddy --restart unless-stopped --network forgejo-stack_forgejo -p 80:80 -p 443:443 -v "$PWD/Caddyfile:/etc/caddy/Caddyfile:ro" -v caddy_data:/data caddy:2--network forgejo-stack_forgejo — та же сеть, что у Forgejo.-p 80:80 -p 443:443 — порты, на которых Caddy общается с интернетом и получает сертификат.-v caddy_data:/data — том, где Caddy хранит сертификаты, чтобы не просить их заново после пересоздания контейнера (так рекомендует документация Caddy).Порты 80 и 443 должны быть открыты в файрволе (например, в UFW), а A-запись уже указывать на сервер, иначе сертификат не выдастся.
Честно о проверке. Настоящий сертификат для настоящего домена мы не выпускали: на стенде нет домена и открытых 80/443. Что делает Caddy на настоящем домене (сам берёт сертификат, обычно у Let's Encrypt, с запасным вариантом, и продлевает его), мы описываем по документации Caddy. Мы проверили всё остальное: связку Caddy и Forgejo с локальным сертификатом. Для этого в Caddyfile стояли тестовое имя git.lab.test и строка tls internal, а сам Caddy слушал порт 18443 на localhost.
Ответ API через Caddy пришёл с кодом 200. Флаг -k нужен только на стенде: он отключает проверку сертификата, потому что tls internal выдаёт локальный. На реальном домене -k не нужен и tls internal не пишите.
Теперь скажем самому Forgejo, что он живёт на https-адресе: ROOT_URL в forgejo/gitea/conf/app.ini должен совпадать с публичным адресом, иначе ссылки клонирования будут неправильными. Мы так же меняли ROOT_URL и SSH_PORT через sed при восстановлении, и это сработало. Рядом в файле лежит и SSH_DOMAIN, посмотрите его тоже.
Помните колонку PORTS? Порт 222 слушает docker-proxy на всех адресах (0.0.0.0:222 и [::]:222). На нашем стенде UFW пропускал только 22, поэтому снаружи 222 не был виден. На сервере без файрвола он доступен всем, и решать надо осознанно. Проверьте с другого компьютера, а не на слово.
git push по SSH снаружи — оставьте 222 и откройте его в UFW сами.'222:22' на '127.0.0.1:222:22', как мы сделали для веба.Forgejo умеет забрать репозиторий из другого места: кнопка «+» в шапке → New migration, затем выбор источника. Мы выбрали обычный Git (форма «Migrate from Git»): она копирует только сам репозиторий по ссылке на клон. Перенос issues и запросов на слияние идёт через плитку конкретного сервиса, для GitHub там нужен токен; этого мы не проверяли.
Форма переноса. Для публичного репозитория достаточно адреса и имени.
Мы проверили это на публичном учебном репозитории octocat/Hello-World: вызвали API миграции с service=git и mirror=false, то есть обычная копия, а не зеркало. Получили репозиторий fjadmin/hello-world: ветка по умолчанию master, три ветки, история на месте (7fd1a60 Merge pull request #6 ..., 7629413, 553c207 first commit).
Перенесённый репозиторий: коммиты и ветки на месте.
Чего мы не проверяли: перенос issues, запросов на слияние и wiki (для него нужен токен доступа в исходном сервисе), зеркало с автообновлением, приватные репозитории и LFS. Не обещаем, что это сработает так же. Начните с одного неважного репозитория и сравните результат с оригиналом.
Бэкап, который никогда не восстанавливали, — это надежда. Мы сделали дамп, развернули его на новом экземпляре рядом и нашли четыре ловушки. Одна из них (хост-ключи SSH) упомянута в официальной документации по бэкапу, но легко теряется из виду; остальные три мы нашли на стенде сами.
Команда forgejo dump собирает один zip-архив: репозитории, базу, настройки. Запускаем от пользователя git и пишем в /tmp внутри контейнера, затем копируем архив на хост:
-c ...app.ini — какой конфиг брать за основу.-f ... — куда записать архив.Finished dumping in file. У нас архив с двумя репозиториями (один из них перенесённый Hello-World) занял 22 МБ.Внутри архива: app.ini, data/ (в ней gitea.db, conf, jwt, actions_log и другое), forgejo-db.sql и repos/. Относитесь к нему как к паролю: в нём конфиг, секреты и все ваши приватные репозитории. И не оставляйте на том же сервере: бэкап рядом с данными пропадает вместе с ними. Копируйте его на другую машину или в S3-совместимое хранилище, а как организовать это на уровне всего сервера, описано в справке о бэкапе сервера.
Что | В дампе? | Что делать |
|---|---|---|
Репозитории, пользователи, заявки, история Actions | да ( | Ничего особенного |
| да | Поправить адреса, если переезжаете |
Хост-ключи SSH сервера ( | нет | Копировать отдельно (ловушка 1) |
| нет, в составе архива их нет | Хранить вместе с бэкапом, но помните, что в конфиге раннера секрет |
Ниже порядок, который у нас сработал. Мы разворачивали дамп в новую папку рядом с живым экземпляром (порты 3001 и 223, имя контейнера forgejo-restore), поэтому адреса в app.ini меняли. При восстановлении на том же адресе, что и раньше, менять их не надо.
Сначала распакуем архив и разложим файлы по местам, где Forgejo их ждёт. Папка repos/ становится git/repositories/, содержимое data/ попадает в gitea/. В адресе 13001 — порт SSH-тоннеля до порта 3001, как 13000 до 3000 в шаге 2.
Дальше два шага, на которых и лежат ловушки. Папка .ssh должна существовать заранее, с владельцем 1000 и правами 700. А базу мы собираем заново из текстового дампа: удаляем файл gitea.db, который приехал в архиве, и создаём новый из forgejo-db.sql с помощью sqlite3, запущенного в том же образе, чтобы ничего не ставить на сервер.
Команда с sqlite3 молчит, если всё в порядке. Теперь запускаем и сразу пересоздаём серверные хуки: это маленькие скрипты внутри репозиториев, которые связывают push с Forgejo, и после переноса путей их надо обновить.
1. Хост-ключи SSH в дамп не входят. Это ключи самого сервера, по ним клиенты узнают, что подключаются туда же, куда и раньше. Новый экземпляр сгенерирует свои, и у всех пользователей появится ошибка «REMOTE HOST IDENTIFICATION HAS CHANGED». Папку forgejo/ssh со старого сервера надо копировать отдельно. Вот отпечатки на нашем стенде: сначала у старого (порт 222) и нового (223), потом после копирования.
Мы копировали папку уже после первого старта, остановив контейнер, и отпечатки совпали. Логичнее положить её до первого запуска, но такой порядок мы отдельно не гоняли. Главное — делайте копию forgejo/ssh вместе с каждым дампом.
2. Не берите data/gitea.db из архива как базу. Копия в архиве оказалась без части последних записей. Скорее всего, они ещё лежали в журнале WAL (SQLite применяет его позже): дамп мы снимали на работающем Forgejo, а официальная документация вообще советует останавливать Forgejo на время бэкапа. Мы взяли эту копию и получили историю Actions 1=running вместо настоящей 2=success 1=failure. Поэтому в рецепте выше базу собирает sqlite3 из forgejo-db.sql.
3. Папка forgejo/git/.ssh должна существовать заранее, с владельцем 1000 и правами 700. Иначе при первом старте на готовой базе контейнер падает с ошибкой уровня [F] (фатальная): inspect authorized_keys failed: open /data/git/.ssh/authorized_keys: permission denied. Причина в том, что entrypoint образа создаёт .ssh от имени root.
4. Не запускайте forgejo admin regenerate keys через docker compose exec. У нас эта команда записала в authorized_keys строку с путём /usr/local/bin/forgejo, а при следующем старте контейнер отказался подниматься: Key on line 2 of /data/git/.ssh/authorized_keys does not exist in database. В логе заодно были подсказки: удалить файл (Forgejo создаст его заново) или выставить SSH_ALLOW_UNEXPECTED_AUTHORIZED_KEYS=true. Без этой команды Forgejo сам создаёт authorized_keys со строкой вида command="/usr/local/bin/gitea --config=/data/gitea/conf/app.ini serv key-1",..., и SSH работает. По документации, при необходимости файл обновляют cron-задачей в админке (Admin → Monitor → Cron); эту задачу мы не проверяли. regenerate hooks проблем не вызывал.
Мы проверили восстановленный экземпляр по пунктам. Вход администратором работает, репозиторий fjadmin/demo на месте, коммиты тоже. История Actions полная: 2=success 1=failure, а не 1=running, как при копировании готовой базы. Так ответил API восстановленного экземпляра (разбирали ответ маленьким скриптом):
fjadmin, администратор;fjadmin/demo, SSH-адрес ssh://[email protected]:223/fjadmin/demo.git;6412cca second run after network fix и f04b88f first commit with CI;runs: 2=success 1=failure.git clone по HTTP отработал. После переноса хост-ключей работает и SSH: связка из authorized_keys и хуков живая, push проходит.
Затем мы сделали новый коммит и отправили его в восстановленный экземпляр. API показал последним коммит 3cd3586 commit after restore. Это и есть проверка: бэкап не просто распаковался, в нём можно работать.
Ещё одна проверка для тех, у кого есть CI. Мы прогнали весь путь заново на чистой папке, выполняя команды из этой статьи по порядку, а на «новом сервере» положили рядом с восстановленными данными папку runner и compose-файл с раннером. Раннер вернулся с записью declared successfully без новой регистрации, подхватил коммит, отправленный после восстановления, и завершил задание как success. Регистрация раннера едет вместе с базой, а вот его config.yml и секрет в нём не едут, поэтому папку держите в своём бэкапе.
Обновление между версиями мы на стенде не проверяли, ниже схема по логике Docker Compose и документации, не наш результат. Порядок осторожный:
:17 — это «любая 17.x», она сама сдвинется при следующем pull. Тег 17.0 держится на последнем патче 17.0.x, а 17 — на последнем миноре 17.x. Для предсказуемости укажите нужный тег (какая версия у вас сейчас, видно в подвале страницы или в ответе /api/v1/version) и на 18 переходите вручную, по официальному руководству по обновлению.docker compose pull, затем docker compose up -d. Проверьте версию тем же curl и зайдите в панель.docker compose exec -u git server forgejo doctor check --all: так велит официальное руководство, мы эту команду не запускали. Руководство: forgejo.org/docs/latest/admin/upgrade/.Чтобы вы не удивились на своём сервере, вот честный список. Всё из него — территория, где вам придётся сверяться с документацией Forgejo.
Проверено на стенде | Не проверялось |
|---|---|
Ubuntu 24.04.5, Docker 29.1.3, Compose v2.40.3 | Ubuntu 22.04, ARM, Windows-раннер |
Forgejo 17.0.1 с SQLite | PostgreSQL и MySQL, rootless-образ, другие версии, обновление между версиями |
Раннер v13.2.1 через общий секрет и сокет Docker | Путь «Create new runner», режим |
Caddy с | Настоящий HTTPS-сертификат на домене |
Миграция публичного репозитория с историей и ветками | Issues, запросы на слияние, wiki, зеркала, приватные репозитории, LFS |
| Бэкап варианта с PostgreSQL, холодный бэкап копией папки, восстановление на другую версию, дамп с LFS и пакетами |
— | Почта (SMTP), реестр пакетов, готовое приложение Forgejo в форме заказа HIP |
В простое сам Forgejo занимал у нас от 100 до 220 МиБ, раннер около 9-15 МиБ; вместе они держались в пределах 150-230 МиБ, а пик по всем контейнерам стенда во время задания составил 328 МиБ (с учётом второго экземпляра Forgejo и Caddy). Для личного репозитория или небольшой команды мы считаем достаточным 1 vCPU и 2 ГБ ОЗУ; это вывод из замеров, а не бенчмарк, а для тяжёлых сборок нужен запас по памяти и диску.
Мы проверили только перенос самого репозитория с историей и ветками. Обычная форма «Migrate from Git» копирует только репозиторий. Перенос issues, запросов на слияние и wiki идёт через плитку конкретного сервиса и требует токена доступа в нём; мы этого не пробовали. Не рассчитывайте на него, пока не проверите на пробном репозитории.
Только для доверенного кода. Сокет смонтирован в контейнер раннера, он управляет демоном хоста; при docker_host: "-" (как у нас) в контейнеры заданий сокет не попадает. Режим automount, который отдаёт его и заданиям, по документации не даёт никакой изоляции. В любом случае раннер выполняет всё, что написано в сценариях. Для чужих репозиториев документация предлагает изолировать раннер в отдельной виртуальной машине или использовать Docker-in-Docker; мы эти варианты не проверяли.
Командой forgejo dump, затем скопируйте архив с сервера. Отдельно сохраните папку forgejo/ssh с хост-ключами, потому что в дамп она не входит. И один раз восстановите архив на пробном экземпляре: базу надо собирать из forgejo-db.sql, а не брать gitea.db из архива.
Проверьте, что контейнер выходит в интернет: docker run --rm alpine wget -qO- https://data.forgejo.org. Если команда не отвечает, ищите проблему в сети Docker или файрволе. Заодно убедитесь, что в конфиге раннера указана сеть Compose в container.network.
Для первой установки домен не нужен: хватит SSH-тоннеля на 127.0.0.1. Для HTTPS домен нужен, на нём Caddy получает сертификат. Порт 222 выбран потому, что 22 занят системным SSH сервера; по нему вы входите на сам сервер, а не в Forgejo.
127.0.0.1:3000, а наружу выставляйте только через Caddy с HTTPS. SSH-порт 222 осознанно откройте или закройте.forgejo-cli actions register), а не устаревшей командой forgejo-runner register.docker_host: "-" сокет не попадает в сами задания, режим automount мы не включали.forgejo/ssh отдельно, иначе у клиентов появится предупреждение о смене ключа.forgejo-db.sql, заранее создайте .ssh (владелец 1000, права 700) и не запускайте regenerate keys через docker compose exec.За вечер реально пройти всю цепочку: compose, установщик, ключ, раннер и первая зелёная сборка. Самое ценное в результате — не сам Forgejo, а уверенность в бэкапе: вы знаете, что архив разворачивается, и знаете четыре места, где он обычно ломается. Если нужен сервер под такой стек, подойдёт VPS в HIP-HOSTING: заказывайте его под нагрузку, которую показывают замеры выше. Перед установкой не забудьте о базовой защите сервера, а после — поставьте бэкап на расписание.