DISABLE_REGISTRATION = true. Аккаунты создаёте вы; никто не регистрируется самостоятельно. Если изредка нужны новые аккаунты, используйте механизм приглашений, а не открывайте дверь настежь.

Собственный Git-сервер.
Репозиторий — это оргструктура.
Приватный репозиторий — это датированная запись того, что вы строите, с кем вы это строите и как далеко вы продвинулись, — а на платформе эта запись хранится компанией в чужой юрисдикции. Шесть шагов, чтобы перенести её на машину, которую вы арендуете анонимно: Forgejo за Caddy, PostgreSQL под ним, SSH push на отдельном порту, раннер Actions, который никогда не видит секретов продакшена, и резервная копия, которую вы действительно восстановили. Машина стоит $3.90 в месяц. Протестировано на Debian 12.
- 01
Выбор
Голый репозиторий, Forgejo, GitLab
- 02
Провизионирование
Порты решены до загрузки
- 03
Развернуть
Forgejo, Postgres, Caddy
- 04
Отправка
SSH, deploy-ключи, подписи
- 05
Сборка
Раннер на расстоянии
- 06
Keep
Зеркала, резервные копии, обновления
Код — наименее интересная часть. Актив — это метаданные.
Почти любой спор о self-hosting Git — это спор об исходном коде, а исходный код — как раз та часть, которая имеет меньше всего значения. Большая его часть ничем не примечательна, значительная доля — это тонкий слой поверх open-source библиотек, и если бы он утёк завтра, практический ущерб обычно свёлся бы к неловкости, а не к вреду. Именно поэтому фраза «мой код не секрет» кажется исчерпывающим ответом — и именно поэтому она им не является.
Интересно то, что окружает код. Репозиторий — это датированная, атрибутированная, высокодетализированная запись о работе: метки времени коммитов описывают, когда вы спите, строки соавторов описывают, с кем вы работаете, имена веток описывают продукты, о которых никто не объявлял, issue-трекер описывает, что сломано и насколько сильно, а первый коммит датирует весь проект. Прочитайте сто репозиториев таким образом — и вы читаете уже не софт, а компанию: её дорожную карту, штат и её проблемы.
На платформе эта запись не принадлежит вам в сколько-нибудь значимом смысле. Она лежит на инфраструктуре, которую вы не контролируете, под аккаунтом, привязанным к личности, которую вы подтвердили, в юрисдикции, чей юридический процесс до неё дотягивается, и сканируется системами, границы которых задаёт документ, который можно изменить. Ничто из этого не требует недобросовестности с чьей-либо стороны — это просто то, что означает аренда трекера. Четыре варианта — и что на самом деле выдаёт каждый из них:
| Где живёт репозиторий | Кто хранит историю | Привязано к вашей юридической личности | Как это идёт не так |
|---|---|---|---|
| Платформа, личный аккаунт | Платформа, в своей юрисдикции | Да — email, платёжные данные, часто номер телефона | Одна блокировка утаскивает за собой все remote |
| Платформа, аккаунт организации | Платформа, плюс кто угодно, кто администрирует организацию | Да, причём и к вашему работодателю тоже | Увольнение лишает вас доступа к собственной работе |
| Self-hosted, у гиперскейлера | Вы — на уровне приложения | Да — платёжный аккаунт и есть личность | Снапшот живёт в аккаунте, который можно закрыть |
| Self-hosted, на VPS без KYC | Вы — и только вы | Нет — ни документов, ни карты, расчёт криптовалютой | Теперь сисадмин — это вы. А плохая резервная копия — это навсегда |
Третья строка — это та строка, которая ловит команды, думающие, что они уже решили этот вопрос. Фордж, установленный вами самостоятельно на машине, арендованной по корпоративной карте под верифицированным аккаунтом компании, вынес приложение с платформы, но оставил личность ровно там же, где она и была. Это реальный выигрыш в контроле — и никакого выигрыша в защите от раскрытия.
А четвёртая строка прямо называет свою цену, потому что остальная часть этого руководства — в основном про то, как её платить. Режим отказа self-hosting — это не слежка, а потеря данных. Платформа хранит резервные копии, о которых вы никогда не задумываетесь. Ваш сервер хранит те резервные копии, которые настроили вы. Шаг 06 — это не необязательный штрих в конце, это шаг, который делает безопасными все остальные пять.
Сколько форджа вам нужно? Честный ответ — часто «нисколько».
Начните с самого малого, что решает вашу задачу, потому что каждый слой поверх него — это сервис, которым придётся управлять. Голый репозиторий по SSH — это одна команда на сервере, который у вас уже есть, и она даёт настоящий Git remote с аутентификацией, историей и доступом на запись. Никакого веб-приложения, никакой базы данных, никакой бесконечной гонки обновлений, нечего патчить, кроме sshd. Если вы один человек, который хочет убрать коммиты с ноутбука и с платформы, это исчерпывающий ответ, и можно перестать читать после шага 02.
# On the server, as an unprivileged user:
mkdir -p ~/git/project.git && cd ~/git/project.git
git init --bare
# On your laptop:
git remote add origin ssh://user@git.example.com/~/git/project.git
git push -u origin main
Фордж нужен, когда появляется второй человек или когда вам нужна любая из четырёх вещей, которые не умеет каталог голых репозиториев: просматривать историю в браузере, ревьюить изменения как pull requests, вести issues рядом с кодом и давать одному человеку доступ к одному репозиторию. Это настоящий шаг вперёд по возможностям и настоящий шаг вперёд по эксплуатационной нагрузке. Выбирайте осознанно:
| Свойство | Голый репозиторий по SSH | Forgejo | Gitea | GitLab CE |
|---|---|---|---|---|
| Веб-интерфейс, pull requests, issues | Нет | Да | Да | Да, и гораздо больше |
| Память в простое | Фактически ноль | Несколько сотен МБ | Несколько сотен МБ | Несколько ГБ, ещё до CI |
| Встроенный CI | git hooks, если вы их напишете | Forgejo Actions | Gitea Actions | GitLab CI, самый зрелый вариант |
| Кто управляет проектом | Проект Git | Codeberg e.V., некоммерческая организация | Коммерческая компания, open core | Публичная компания, open core |
| Лицензия | GPLv2 | GPLv3+ начиная с v9.0 (до этого MIT) | MIT, с проприетарными надстройками | Ядро MIT, проприетарные тарифы |
| Выбирайте, когда | Вы одни и хотите нулевого обслуживания | Вам нужен настоящий фордж на маленькой машине | Вы уже используете его, и он работает | Вам нужен корпоративный набор функций |
У Forgejo и Gitea общий предок, и в повседневном использовании они до сих пор выглядят почти одинаково. Forgejo стал полноценным форком в начале 2024 года и принадлежит Codeberg e.V. — некоммерческой зарегистрированной ассоциации в Берлине; Gitea разрабатывает коммерческая компания, которая владеет её товарным знаком и требует от контрибьюторов передачи авторских прав. Оба варианта — хороший софт. Но только у одного из них хранитель проекта имеет интересы, совпадающие с причиной, по которой вы читаете эту страницу.
В остальной части этого руководства используется Forgejo. Всё, что описано в шагах с 02 по 06, применимо к Gitea практически дословно — меняются имя контейнера и несколько переменных окружения, но не сама структура.
Определите порты, прежде чем фордж определит их за вас. Два из них — это SSH.
Сначала закажите машину. В панели: Order → VPS → Sentinel, образ Debian 12, в любом бастионе, где вы хотите, чтобы жила ваша история, — Helsinki, Stockholm, Oslo или Reykjavík. Для открытия аккаунта не требуется email, а счёт оплачивается в Monero, Bitcoin, Lightning или любом другом поддерживаемом активе. Здесь это важнее, чем для большинства нагрузок: фордж — единственный сервис, вся ценность которого в том, что запись о вашей работе не привязана к имени.
Затем пройдите чек-лист укрепления защиты в первый час — SSH только по ключу на перенесённом порту, автоматические обновления, файрвол с политикой default-deny — потому что всё дальнейшее предполагает машину, которая ещё не стала чьей-то ещё.
Фордж — это не статический сайт с логином. Это публичное веб-приложение с прикрученным к нему движком выполнения кода. Два параметра по умолчанию, если их не трогать, превращают его в бесплатный сервис для посторонних: открытая регистрация, которая заполняет инстанс спам-аккаунтами в течение нескольких дней после появления сертификата в логах прозрачности, и CI, запускающий рабочие процессы от кого угодно, кто может открыть pull request, — именно так self-hosted раннер в итоге начинает майнить криптовалюту для кого-то другого. Ни то ни другое не является тонкой атакой, и ни то ни другое не редкость. Оба закрываются одной настройкой каждое, на шаге 03 и шаге 05 — но решите прямо сейчас, что вы их закроете, потому что промежуток между «это работает» и «это уже используют» измеряется днями.
Направьте на машину доменное имя — A-запись и AAAA, если нужна доступность по IPv6, — и дайте ему распространиться, прежде чем начинать, потому что обратный прокси попытается получить сертификат на это имя при первом запуске, а проваленная проверка — так себе способ начать. Затем пропишите правила файрвола, пока ещё ничего не слушает порты.
Проблема двух SSH. Фордж использует SSH для Git, а ваш сервер уже использует SSH для администрирования. Оба не могут владеть портом 22, и именно из-за их смешивания люди получают административный демон, которому вдобавок приходится разбираться в Git. Держите их раздельно: администрирование — на перенесённом порту из руководства по укреплению защиты, Git — на отдельном публичном порту, который пробрасывается в контейнер. 2222 — общепринятый выбор, и в нём нет ничего сакрального.
ufw default deny incoming
ufw allow 47222/tcp # administrative SSH, moved
ufw allow 80/tcp # ACME HTTP-01 challenge
ufw allow 443/tcp # the web interface
ufw allow 2222/tcp # git over SSH, into the container
ufw enable
ufw status numbered
# Nothing else is open. In particular: PostgreSQL stays on the Docker
# network and is never published to the host, let alone the internet.
Устанавливайте Docker из официального репозитория, а не из пакета дистрибутива, чтобы Compose v2 был доступен, а версия оставалась актуальной. Затем создайте каталог для стека и сделайте его единственным местом на машине, которое хранит состояние — именно этот каталог резервирует шаг 06.
curl -fsSL https://get.docker.com | sh
docker compose version
install -d -m 0750 /srv/forgejo
cd /srv/forgejo
Три контейнера. Один из них не должен быть доступен никогда.
Forgejo, PostgreSQL и Caddy. Forgejo хранит репозитории и отдаёт интерфейс, PostgreSQL хранит всё, что не является объектом Git, а Caddy терминирует TLS и получает сертификат, не переспрашивая дважды. PostgreSQL намеренно не публикуется на хост: он доступен только по сети Compose, по имени, из того единственного контейнера, которому это нужно.
/srv/forgejo/.env
FORGEJO_DOMAIN=git.example.com
FORGEJO_VERSION=15 # the LTS line; 16 is current stable
POSTGRES_PASSWORD=$(openssl rand -base64 32) # paste the value, do not leave the $( )
/srv/forgejo/docker-compose.yml
services:
forgejo:
image: codeberg.org/forgejo/forgejo:${FORGEJO_VERSION}
container_name: forgejo
environment:
USER_UID: 1000
USER_GID: 1000
FORGEJO__database__DB_TYPE: postgres
FORGEJO__database__HOST: db:5432
FORGEJO__database__NAME: forgejo
FORGEJO__database__USER: forgejo
FORGEJO__database__PASSWD: ${POSTGRES_PASSWORD}
FORGEJO__server__DOMAIN: ${FORGEJO_DOMAIN}
FORGEJO__server__ROOT_URL: https://${FORGEJO_DOMAIN}/
FORGEJO__server__SSH_DOMAIN: ${FORGEJO_DOMAIN}
FORGEJO__server__SSH_PORT: "2222" # what clones will say
FORGEJO__server__SSH_LISTEN_PORT: "22" # what the container listens on
FORGEJO__service__DISABLE_REGISTRATION: "true"
FORGEJO__repository__DEFAULT_PRIVATE: private
FORGEJO__security__INSTALL_LOCK: "true"
restart: unless-stopped
volumes:
- ./data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "127.0.0.1:3000:3000" # web, reached only by Caddy
- "2222:22" # git over SSH, public
depends_on:
- db
db:
image: postgres:16-alpine
container_name: forgejo-db
environment:
POSTGRES_USER: forgejo
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: forgejo
restart: unless-stopped
volumes:
- ./postgres:/var/lib/postgresql/data
# No ports: block. This is the point.
caddy:
image: caddy:2-alpine
container_name: forgejo-caddy
restart: unless-stopped
network_mode: host
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy-data:/data
- ./caddy-config:/config
/srv/forgejo/Caddyfile
git.example.com {
encode zstd gzip
request_body {
max_size 512MB # raise it if you push large repositories
}
reverse_proxy 127.0.0.1:3000
}
Две строки про SSH — это как раз то, где чаще всего ошибаются, так что их стоит прочитать дважды. SSH_LISTEN_PORT — это порт внутри контейнера, на котором биндится встроенный SSH-сервер Forgejo. SSH_PORT — это порт, который Forgejo печатает в URL для клонирования, которые он вам показывает, — он должен совпадать с публичной стороной проброса портов. Рассинхронизируйте их — и всё будет работать, кроме той единственной вещи, которую пользователи копируют и вставляют.
Запустите его, затем создайте администратора из командной строки. Не используйте веб-установщик. На свежем инстансе он доступен любому, кто найдёт доменное имя, а первый аккаунт, созданный через него, становится администратором — гонка, в которой вам не нужно участвовать. INSTALL_LOCK в файле выше закрывает его ещё до того, как контейнер вообще запустится.
docker compose up -d
docker compose logs -f forgejo # wait for "Starting new Web server"
docker compose exec -u git forgejo forgejo admin user create \
--admin --username you --email you@example.com --random-password
Откройте доменное имя в браузере, войдите с напечатанным паролем и смените его. Затем пройдитесь по настройкам, которые превращают работающий инстанс в закрытый — большинство из них уже заданы блоком окружения выше, и каждую стоит проверить, а не считать само собой разумеющейся:
DEFAULT_PRIVATE = private. Новые репозитории изначально закрыты. Публикация становится осознанным действием, а не следствием настройки по умолчанию.
REQUIRE_SIGNIN_VIEW = true скрывает даже публичные репозитории от посетителей без входа в систему. Правильно для приватного командного форджа; неправильно, если вы хотите что-то публиковать.
Включите это на аккаунте администратора прежде, чем делать что-либо ещё. Теперь фордж — это машина, способная переписать любую вашу ветку.
Один путь стоит запомнить: боевая конфигурация лежит в ./data/gitea/conf/app.ini на хосте. Forgejo сохранил имя каталога gitea ради совместимости с существующими установками, и это удивляет каждого — ровно один раз. Переменные окружения вида FORGEJO__section__KEY при старте записываются в этот файл, так что всей конфигурацией можно управлять из файла Compose, — но если вы отредактируете app.ini вручную, переменная окружения для того же ключа перезапишет вашу правку при следующем перезапуске. Выберите один из двух способов и придерживайтесь его.
Отправляйте по SSH. А затем подписывайте, чтобы история не зависела от сервера.
Вставьте свой публичный ключ в настройки профиля, а затем научите свой клиент, где находится фордж. Поскольку Git работает на нестандартном порту, запись в вашем SSH-конфиге стоит тех тридцати секунд — она убирает порт из каждого URL, который вам когда-либо придётся набирать, и делает remote переносимыми.
# ~/.ssh/config
Host git.example.com
Port 2222
User git
IdentityFile ~/.ssh/id_ed25519
# Then, in any repository:
git remote set-url origin git@git.example.com:you/project.git
git push -u origin main
ssh -T git@git.example.com # expect a greeting, not a shell
Именно последняя команда говорит вам, согласны ли друг с другом проброс портов, SSH-сервер контейнера и ваш ключ. Приветствие от Forgejo означает, что вся цепочка работает. Запрос пароля означает, что вместо этого вы попали на административный sshd хоста, и проброс портов настроен неверно.
Теперь подпишите свои коммиты. Это тот шаг, который пропускает большинство руководств по self-hosting, а на self-hosted фордже он важнее, чем на платформе. Поле автора коммита — это произвольный текст: любой, кто может сделать push, может вписать туда ваше имя. Подпись — это доказательство того, что конкретный ключ создал конкретный коммит, и это доказательство остаётся в силе даже после того, как сервер, на который был отправлен push, скомпрометирован, перенесён или выброшен. Git умеет подписывать тем же SSH-ключом, который у вас уже есть, так что обходной путь через PGP больше не нужен:
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true
git commit --allow-empty -m "signed"
git log --show-signature -1
Затем добавьте тот же публичный ключ в фордже во второй раз — как ключ для подписи, а не для аутентификации. Forgejo проверяет подпись на стороне сервера и показывает коммит как verified; без этой второй записи у него нет способа связать подпись с вашим аккаунтом, и каждый коммит будет показан как unverified, даже если подписан правильно.
Deploy-ключи — для машин, которым нужно только читать. Продакшен-серверу, сборочной машине или зеркалу не нужен пользовательский аккаунт на фордже. Вместо этого выдайте deploy-ключ: пара ключей, добавленная к одному репозиторию, только для чтения, если явно не разрешена запись, и не дающая доступа ни к чему другому на инстансе. Отозвать его — значит удалить одну запись. Это самая эффективная привычка для поддержания порядка на self-hosted фордже, потому что пользовательские аккаунты накапливают права, а deploy-ключи — нет.
Миграция с платформы — удобный момент сделать всё это как следует. Мастер New Migration в Forgejo берёт URL репозитория и токен доступа и переносит ветки, теги, issues, комментарии, метки, вехи, релизы и pull requests. Сначала прогоните один репозиторий, чтобы увидеть форму результата, ожидайте, что лимиты API будут замедлять крупные партии, и держите источник в режиме только для чтения несколько недель, а не удаляйте его в тот же день.
CI-раннер — это машина, которая выполняет чужой код. Размещайте его соответственно.
Forgejo Actions читает файлы рабочих процессов в том же формате, что и GitHub Actions, из .forgejo/workflows/, и передаёт их отдельной программе под названием Forgejo Runner. Раннер не является частью форджа и не обязан жить на той же машине — в этом весь замысел, и именно поэтому этот шаг сопровождается предупреждением, а не пожатием плеч.
Включите Actions на инстансе, затем получите токен регистрации из панели администрирования — на весь инстанс или ограниченный одной организацией или одним репозиторием, что является более безопасным выбором по умолчанию, если вы не уверены. Раннер сам подключается наружу к форджу, поэтому ему вообще не нужны входящие порты, и он может находиться за любым файрволом, в том числе на машине без публичного адреса.
# In the Compose environment of the forge, or app.ini:
# FORGEJO__actions__ENABLED: "true"
# Then: Site administration -> Actions -> Runners -> Create new runner
# On the runner machine:
forgejo-runner generate-config > config.yml
forgejo-runner register --no-interactive \
--instance https://git.example.com \
--token <REGISTRATION_TOKEN> \
--name runner-01 \
--labels docker:docker://node:22-bookworm,debian:docker://debian:12
forgejo-runner --config config.yml daemon
Метки — это контракт между рабочим процессом и раннером: задача, которая запрашивает runs-on: docker, получает контейнер, собранный из образа с правой стороны. Объявляйте только те метки, которые вы действительно готовы обслуживать, потому что задача, запрашивающая метку, которую никто не предлагает, будет ждать вечно, а не завершится с ошибкой, — а это неприятно отлаживать в конце долгого дня.
.forgejo/workflows/ci.yml
on: [push, pull_request]
jobs:
test:
runs-on: docker
container:
image: node:22-bookworm
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
Раннер выполняет всё, что написано в файле рабочего процесса, а файл рабочего процесса приходит в составе pull request. Если хоть кто-то за пределами вашей команды может его открыть, раннеру место на машине, отдельной от форджа — второй Sentinel за $3.90 в месяц, без доступа к каталогу данных форджа, без учётных данных базы данных и без ничего в окружении, что вам было бы неприятно увидеть в логе сборки. На самом фордже требуйте подтверждения перед запуском рабочих процессов для новых контрибьюторов. Два стандартных провала одинаково скучны и одинаково дорого обходятся: майнинговая нагрузка, спрятанная в pull request, и секрет, напечатанный шагом, который кто-то добавил намеренно.
Секреты ограничивайте по тому же принципу. Forgejo хранит их на уровне репозитория, организации и инстанса; deploy-ключ, нужный только одному репозиторию, должен существовать только в нём, чтобы скомпрометированный рабочий процесс в несвязанном проекте не мог его прочитать. Предпочитайте недолговечные токены долгоживущим везде, где это поддерживает целевая система, и никогда не помещайте секрет в файл рабочего процесса — этот файл лежит в репозитории, а у репозитория есть история.
Если ваши сборки при каждом запуске тянут одни и те же базовые образы, кешируйте их. Локальное зеркало реестра или постоянный кеш раннера превращает двухминутную загрузку образа в двухсекундную, и это вежливо по отношению к вышестоящим реестрам, которые вы забрасываете запросами. И обратите внимание на паттерн, к которому подталкивает весь этот замысел, — именно поэтому это руководство соседствует с руководством по агентному рантайму: раннер — это универсальный воркер, забирающий задачи из вашего форджа, а значит, там же самое место для плановых регламентных задач, конвейеров данных и агентных нагрузок.
Каждый клон — это резервная копия кода. Резервной копии разговора не существует.
Git здесь необычно всепрощающий, и стоит точно объяснить почему. Каждый клон — это полная копия истории, так что пока у хотя бы одного разработчика есть актуальная рабочая копия, код переживает сервер. Это по-настоящему успокаивает — и покрывает ровно одну из двух вещей, которые хранит ваш фордж. Другая — issues, обсуждения pull request, ветки ревью, релизы, страницы wiki, пользователи, команды, права доступа, история CI — существует только в базе данных, ни у кого нет её клона, и это обычно та часть, на накопление которой ушло три года.
Сначала push-зеркало. Оно бесплатное и работает само по себе. В репозитории: Settings → Mirror settings → добавьте push-зеркало с URL и токеном доступа для назначения. После этого Forgejo реплицирует каждую ветку и тег после каждого push — во второй Forgejo, в Codeberg или на платформу, которую вы решили оставить в роли витрины, а не дома. Одна оговорка, которую стоит проговорить прямо: push-зеркало добросовестно публикует заново всю историю, включая секрет, который вы собирались удалить.
Затем настоящая резервная копия — и это не встроенный архив. В Forgejo есть команда forgejo dump, которая упаковывает конфигурацию, репозитории, данные и дамп базы данных в единый zip-архив. Это по-настоящему полезная вещь, чтобы передать мейнтейнеру при сообщении об ошибке. Но не то, на что стоит полагаться, чтобы вернуться с того света: SQL, который она производит, имеет давно известные проблемы при повторном импорте, а архив каждый раз пересобирает весь набор репозиториев заново, что на реальном инстансе означает большой файл, который пишется с нуля каждую ночь. Используйте вместо этого скучный, проверяемый путь — сделайте дамп базы данных её собственным инструментом и заберите каталог данных в резервную копию как файлы:
#!/bin/sh
# /usr/local/bin/forgejo-backup — run from a systemd timer, nightly
set -eu
cd /srv/forgejo
# 1. Consistent database dump, written next to the data.
docker compose exec -T db pg_dump -U forgejo -Fc forgejo > ./db.dump
# 2. Everything that matters, to an encrypted offsite repository.
restic backup ./data ./db.dump ./docker-compose.yml ./.env ./Caddyfile
# 3. Retention, then prove the repository is still readable.
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
restic check
Отправляйте её на машину в другой юрисдикции, отличной от форджа, через append-only репозиторий, чтобы компрометация форджа не могла стереть собственную историю резервных копий — руководство по шифрованным резервным копиям — это развёрнутая версия трёх строк выше, включая таймер systemd и ограничение ключа режимом append-only.
Затем один раз, намеренно, восстановите её. Закажите тестовый VPS, восстановите на него снапшот, поднимите тот же файл Compose поверх восстановленного каталога данных, загрузите дамп базы данных, направьте на него запись в hosts-файле и войдите в систему. Засеките время на всё это. Именно это число — а не сам факт наличия задания резервного копирования — и есть ваша реальная позиция по восстановлению, и у каждого, кто проделывал это упражнение, в первый раз находилась хотя бы одна вещь, пропущенная в списке включения.
# restic keeps absolute paths, so the stack reappears under the target.
restic restore latest --target /srv/restore
cd /srv/restore/srv/forgejo
docker compose up -d db # empty volume: the entrypoint creates the database
docker compose exec -T db pg_restore -U forgejo -d forgejo \
--clean --if-exists --no-owner < db.dump
docker compose up -d # then sign in and walk the issue tracker
Обновления, кратко. Закрепляйте образ за мажорной версией, а не за :latest, чтобы автоматический pull не перенёс вас через breaking change, пока вы спите. Читайте release notes перед пересечением мажорной границы, сначала делайте снапшот и помните, что миграция базы данных запускается при первом старте новой версии — а это дверь в один конец. Автообновление в стиле Watchtower — неплохая идея для статического сайта и плохая для машины, которая хранит все ваши ветки.
Четыре проверки, прежде чем считать дело сделанным. Выйдите из системы и убедитесь, что инстанс не предлагает никакого способа создать аккаунт. Из браузера без сессии убедитесь, что приватный репозиторий невидим, а не просто запрещён. Убедитесь, что раннер не может прочитать каталог данных форджа. И убедитесь, что последняя проверка restic check прошла успешно — задание резервного копирования, которое молча проваливается, есть у каждого, вплоть до того дня, когда оно понадобится.
Вы убрали одну сторону. Вы не убрали здание.
Self-hosting форджа — это реальное изменение того, кто держит вашу работу, и это не невидимость. Четыре вещи остаются верными после того, как всё описанное выше сделано, и знание о них — это разница между настоящей защитой и историей, которую вы рассказываете сами себе.
Диск находится в чужом здании. Арендованный сервер работает на железе в дата-центре с дверями и персоналом. NordBastion не инспектирует клиентские нагрузки, а отчёт о прозрачности и warrant canary описывают, что произошло бы, если бы нас об этом попросили, — но честное утверждение — архитектурное: меняется то, что не существует индекса ваших репозиториев на уровне продукта, аналитического конвейера над вашими коммитами и политики конфиденциальности, которую можно изменить задним числом. Не меняется то, что машина физическая и находится не у вас дома.
Доменное имя становится публичным в момент выпуска сертификата. Каждый публично доверенный сертификат публикуется в логах прозрачности сертификатов, которые может искать кто угодно, так что git.yourproject.com можно обнаружить, даже если вы никогда на него не ссылались. Это утечка не вашего кода, а факта, что проект существует и примерно когда он начался. Используйте субдомен уже публикуемого вами имени либо вовсе откажитесь от публичного сертификата и обращайтесь к форджу через WireGuard или onion-сервис.
Теперь вы патчите веб-приложение. Фордж — это страница входа, конвейер рендеринга недоверенной разметки, SSH-сервер и движок CI, и всё это доступно из интернета. Это значительно большая поверхность атаки, чем голый репозиторий из шага 01, и её нужно постоянно поддерживать в актуальном состоянии. Подпишитесь на анонсы релизов и относитесь к security-релизу как к задаче на эту же неделю, а не на этот же квартал.
Федерации пока ещё нет. Forgejo ведёт настоящую работу над федерацией форджей и публикует новости о прогрессе, но на сегодняшний день вы не можете открыть pull request на чужом инстансе со своего собственного так, как это можно на общей платформе. На практике это значит, что внешнему контрибьютору всё ещё нужен аккаунт на вашем фордже, либо вы принимаете патчи по почте, либо держите публичное зеркало для случайных контрибьюций. Заранее выберите тот из трёх вариантов, который подходит вашему проекту, а не удивляйтесь этому потом.
Что вы на самом деле купили, если точно: датированная запись о том, что вы строите, с кем и когда, больше не хранится компанией, чья бизнес-модель включает её анализ. Это узкое утверждение, но оно настоящее. Оно стоит своих денег только в том случае, если сделан шаг 06 — self-hosted фордж без проверенного восстановления не является улучшением приватности, это единая точка отказа с более красивым брендингом.
Фордж маленький. CI — нет.
| Тариф | Ежемесячно | Ресурсы | Что на нём комфортно работает |
|---|---|---|---|
| Sentinel | $3.90 | 2 vCPU · 4 GB · 120 GB | Forgejo + PostgreSQL для одного человека или небольшой команды. Правильное начало. |
| Второй Sentinel | $3.90 | Раннер на отдельной машине | Изоляция, которая действительно важна, по цене чашки кофе |
| Garrison | $7.90 | 4 vCPU · 8 GB · 240 GB | Фордж на десять человек, container registry и артефакты |
| Ravelin | $16.90 | 8 vCPU · 16 GB · 480 GB | Загруженный CI, большие репозитории, несколько параллельных задач |
Ничто в этой таблице не рассчитано под Git. Forgejo и PostgreSQL вместе в простое занимают пару сотен мегабайт; машину заполняет CI — слои контейнеров, кеши сборки, артефакты — и содержимое репозитория, которое не является исходным кодом. Считайте в первую очередь диск, во вторую — ядра, и в последнюю — память. Здесь две маленькие машины лучше одной большой, потому что вторая — это граница безопасности, а не запас мощности.
Шесть способов всё испортить. Два из них стоят денег.
Открытая регистрация — и фордж, полный посторонних
Сертификат появляется в логах прозрачности, сканеры подтягиваются в течение нескольких дней, и инстанс с включённой регистрацией заполняется спам-аккаунтами и спам-репозиториями. Установите DISABLE_REGISTRATION до первого запуска, а не после первого инцидента.
Раннер, который майнит криптовалюту для кого-то другого
CI, запускающий рабочие процессы из любого pull request, — это бесплатные вычисления для того, кто его найдёт. Требуйте подтверждения для новых контрибьюторов, держите раннер на отдельной машине и время от времени просматривайте длительность задач.
Неверный ROOT_URL — и всё работает наполовину
URL для клонирования указывают не на тот хост, вебхуки срабатывают не по тому адресу, обратные вызовы OAuth не проходят, а аватары ломаются. Интерфейс выглядит нормально, поэтому никто не подозревает одну-единственную строку. Задавайте DOMAIN, ROOT_URL, SSH_DOMAIN и SSH_PORT вместе и проверяйте URL для клонирования прямо в интерфейсе.
Два SSH-сервера, один порт
Контейнеру нужен порт 22, а хост его уже занял, поэтому проброс молча уходит куда-то не туда, и git push запрашивает пароль. Сначала перенесите административный SSH, опубликуйте контейнер на отдельном порту и проверьте это через ssh -T.
Дамп, который никогда не восстанавливали
Встроенный архив — это вспомогательный артефакт для поддержки, а не проверенный путь восстановления, и его SQL имеет известные проблемы при повторном импорте. Делайте дамп базы данных через pg_dump, резервируйте каталог данных как файлы и один раз восстановите всё целиком на тестовой машине.
Диск заполняется слоями сборки, а не кодом
Исходный код крошечный; слои контейнеров, артефакты и кеши CI — нет. Машина на 120 ГБ с усердным пайплайном заполняется за недели. Чистите образы по таймеру, ограничивайте срок хранения артефактов и следите за диском, а не за процессором.
Вопросы, и ответы на них.
Одиннадцать вопросов, которые возникают при построении этого — начиная с двух, которые решают, стоит ли вообще это строить.
Forgejo или Gitea — что на самом деле ставить?
Forgejo — для большинства, и причина тут в управлении проектом, а не в функциях. У двух проектов общий предок, и пользоваться ими до сих пор почти неотличимо: тот же бинарник на Go, тот же скромный расход ресурсов, тот же веб-интерфейс, сопоставимый CI. Разница в том, кто ими управляет. Forgejo принадлежит Codeberg e.V. — некоммерческой зарегистрированной ассоциации в Берлине, и стал полноценным форком в начале 2024 года; всё публикуется как свободное ПО, и от контрибьюторов не требуют передачи авторских прав. Gitea разрабатывает коммерческая компания, которая владеет товарным знаком и доменом, требует от контрибьюторов передачи авторских прав и работает по модели open core с платными функциями поверх бесплатной базы. Ни один из этих фактов не делает Gitea плохим софтом. Но весь смысл переноса репозиториев с платформы в том, чтобы перестать зависеть от чужой дорожной карты, поэтому выбор форка, за которым стоит некоммерческая организация, — решение, которое соответствует причине, по которой вы вообще здесь. Практическое замечание на случай перехода позже: Forgejo можно установить поверх существующего каталога данных Gitea, а в обратную сторону это не поддерживается.
Нужен ли мне вообще фордж? Разве голого репозитория по SSH не достаточно?
Для одного человека с одним ноутбуком и привычкой писать внятные сообщения коммитов голого репозитория действительно достаточно, и это самая надёжная вещь во всём руководстве: git init --bare на сервере, обычный SSH-аккаунт — и нет ни веб-приложения, которое надо патчить, ни базы данных, которую надо бэкапить, ни поверхности атаки шире, чем sshd. Такая конфигурация никогда не устареет. Фордж нужен в тот момент, когда появляется второй человек, или когда вам нужна любая из четырёх вещей, о которых голый репозиторий понятия не имеет: просматриваемая в браузере история, pull requests с ревью, issue-трекер рядом с кодом и контроль доступа по каждому репозиторию отдельно. Это не небольшой шаг вперёд — это разница между хранилищем и совместной работой. Будьте честны с собой насчёт того, какую из двух задач вы решаете, потому что фордж — это сервис, которым вы теперь управляете, а голый репозиторий — просто каталог.
Можно ли перенести мои репозитории с GitHub вместе с issues и pull requests?
Да, и это одна из лучших историй миграции среди self-hosted решений. Встроенный инструмент миграции Forgejo берёт URL репозитория и персональный токен доступа и переносит куда больше, чем просто код: ветки и теги, issues с комментариями, pull requests, метки, вехи и релизы. Выдайте ему токен с правом чтения, сначала прогоните одну миграцию, чтобы увидеть, как выглядит результат, а затем переходите к остальным. Стоит ожидать три вещи. Крупные или многочисленные миграции упрутся в лимиты API исходной платформы и просто займут больше времени — запустите их и оставьте в покое. Вложения в issues подтягиваются по сети, поэтому инстанс за строгим файрволом на исходящий трафик получит issues с битыми изображениями. А ветки обсуждений ревью не всегда попадают точно туда, где были, потому что модели данных у платформ не идентичны. Планируйте держать исходные репозитории в режиме только для чтения несколько недель, а не удалять их в тот же день.
Self-hosted фордж действительно приватнее — или просто другой?
Он действительно другой в одном конкретном и важном смысле: индексом не владеет никто, кроме вас. На платформе приватный репозиторий приватен для других пользователей, но не для самой платформы — она его хранит, сканирует, строит по нему граф зависимостей и может быть обязана предоставить его по решению суда в своей юрисдикции. Этот индекс необычно красноречив, даже если код скучный: метки времени коммитов описывают, когда вы спите, соавторы описывают вашу команду, имена веток описывают ещё не анонсированные продукты, а первый коммит датирует весь проект. Перенос на собственный сервер полностью убирает платформу из этой картины. Чего он не делает — так это не убирает саму машину. Вы всё ещё арендуете железо в дата-центре, диск всё ещё физически доступен людям, которым принадлежит здание, а выбранное вами доменное имя всё ещё попадает в логи прозрачности сертификатов. Честный итог: вы заменили компанию с договорной политикой конфиденциальности сервером, единственная политика которого — ваша собственная конфигурация, в юрисдикции, которую вы выбрали осознанно, — а аккаунт, с которого идёт оплата, не обязан носить ваше имя.
Насколько большим должен быть VPS?
Меньше, чем приучила всех ожидать документация GitLab. Forgejo и Gitea — это одиночные бинарники на Go, которые в простое занимают пару сотен мегабайт памяти; добавьте PostgreSQL и обратный прокси — и фордж для одного разработчика спокойно помещается в Sentinel: 2 vCPU, 4 ГБ, 120 ГБ NVMe, $3.90 в месяц — и ещё с запасом. Нагрузка приходит из трёх других мест, и каждое стоит закладывать в расчёт осознанно. CI — самое существенное: раннер, собирающий контейнеры, займёт все выделенные ему ядра и забьёт диск слоями. Размер репозитория — второе: игровые ассеты, Jupyter-ноутбуки и vendored-зависимости растут куда быстрее исходного кода. Артефакты и container registry — третье. Правило, которое хорошо работает на практике: держите фордж на Sentinel, держите раннер на отдельном втором Sentinel и переходите на тариф выше только тогда, когда об этом просит диск, а не процессор.
Совместим ли Forgejo Actions на самом деле с моими рабочими процессами GitHub Actions?
Достаточно совместим, чтобы большинство несложных рабочих процессов запускались без изменений, но недостаточно, чтобы переносить сложный пайплайн, не читая его. Формат файла рабочего процесса тот же самый, он лежит в .forgejo/workflows/ (а .github/workflows/ тоже читается), и общие строительные блоки — checkout, настройка языка, кеш, matrix-сборки — ведут себя ожидаемо, потому что раннер выполняет их в контейнерах точно так же. Ломается то, что полагается на GitHub именно как на платформу, а не на Actions как на формат: экшены из маркетплейса, привязанные к URL на github.com, которые ваш инстанс должен получить разрешение забирать, экшены, вызывающие GitHub REST API за метаданными релиза или PR, образы раннеров GitHub с очень специфичным предустановленным набором инструментов, и облачная аутентификация на основе OIDC. Рабочая схема миграции: начните с рабочего процесса, который только собирает и тестирует, добейтесь, чтобы он проходил успешно, а затем добавляйте шаги деплоя по одному.
Что будет с моим кодом, если VPS выйдет из строя?
Сам код — это как раз та часть, с которой всё в порядке, и в этом тихое преимущество Git перед почти любым другим self-hosted сервисом: каждый клон — это полная копия истории, так что пока у хотя бы одного разработчика есть актуальная рабочая копия, репозиторий не потерян. Вместе с сервером умирает всё, что вокруг кода — issues, обсуждения pull request, комментарии ревью, релизы, wiki, пользователи, правила доступа, история CI. Это существует только в базе данных, ни у кого нет её клона, и обычно это как раз та часть, на накопление которой ушли годы. Поэтому относитесь к этим двум вещам по-разному. Для кода настройте push-зеркало во второе место — и вы получите непрерывную, автоматическую защиту за пределами машины без каких-либо усилий. Для метаданных нужна настоящая резервная копия: дамп PostgreSQL плюс каталог с данными, зашифрованные и по расписанию отправляемые на машину в другой юрисдикции. Шаг 06 закрывает оба случая, и именно тренировочное восстановление в конце превращает их из намерения в гарантию.
Можно ли оставить GitHub как зеркало, а не уходить с него полностью?
Да, и для многих проектов это правильный ответ, а не компромисс. Forgejo поддерживает push-зеркала, так что ваш инстанс остаётся origin, с которым работает команда, а каждый push автоматически реплицируется в репозиторий на GitHub, Codeberg или втором Forgejo. Публичная видимость, случайные контрибьюторы, реестры пакетов, ожидающие URL на GitHub, и ссылка на issue-трекер в старом README — всё продолжает работать, а источник истины — вместе с приватными репозиториями, логами CI и историей issues всего, что вы не публикуете, — остаётся на вашей машине. Это работает и в обратную сторону: pull-зеркала хранят локальную, всегда актуальную копию сторонних проектов, от которых вы зависите, — и это стоит делать для всего, без чего вы окажетесь в тупике, если это удалят завтра. Единственное, к чему стоит подходить осознанно, — это то, что именно вы зеркалируете: push-зеркало репозитория, в истории которого остался секрет, который вы собирались удалить, немедленно и автоматически публикует этот секрет заново.
Как дать подрядчику доступ к одному репозиторию, не открывая доступ ко всем остальным?
Выбирайте уровень доступа, соответствующий тому, как долго он должен действовать. Для человека, который будет читать, коммитить и открывать pull requests, создайте пользовательский аккаунт и добавьте его как участника (collaborator) именно к тем репозиториям, которые ему нужны, с явным уровнем прав — read, write или admin, — а не включайте его в команду организации, которая незаметно даёт больше прав. Для автоматизированной системы, которой нужен только fetch — цель деплоя, сборочная машина, зеркало, — используйте deploy-ключ только для чтения вместо пользовательского аккаунта: он привязан к одному репозиторию, не даёт ничего сверх этого, а отозвать его — значит удалить одну строку. Для скриптов, которые обращаются к API, выпускайте токен доступа с ограниченной областью действия, а не передавайте пароль. Всё это держится на двух привычках: держите регистрацию закрытой, чтобы существовали только те аккаунты, которые создали вы сами, и ставьте в календарь дату, когда выдаёте временный доступ, потому что ни один фордж никогда не напомнит вам, что контракт закончился.
Нужно ли форджу доменное имя и не раскрывает ли это мою личность?
Ему нужно доменное имя для веб-интерфейса, потому что TLS-сертификаты выдаются на имена, а браузеру не следует доверять здесь IP-адресу. Но это имя не обязано быть прослеживаемым до вас. Три вещи, к которым стоит подойти осознанно. Регистрируйте домен там, где не требуют личность, о сохранности которой вы заботились в другом месте, и держите приватность WHOIS включённой — регистратор — это отдельное от хостинга решение о доверии, и именно про него все забывают. Ожидайте, что доменное имя станет публичным в момент выпуска сертификата, потому что каждый публично доверенный сертификат публикуется в логах прозрачности сертификатов, которые может искать кто угодно; wildcard-сертификат на домене, которым вы уже пользуетесь, избавляет от необходимости добавлять новое имя, доступное для поиска. А если вы предпочли бы вообще не иметь публичного имени, такая альтернатива реальна: разместите фордж как onion-сервис Tor или сделайте его доступным только через WireGuard, полностью откажитесь от публичного сертификата и смиритесь с тем, что вместе с этим уходит удобство открыть его в браузере откуда угодно.
Не возражает ли NordBastion, если я запущу Git-фордж и раннеры CI на VPS?
Ничуть — фордж с раннерами — это обычная нагрузка, и это одна из самых частых вещей, которые строят наши клиенты. В политике допустимого использования ничего не сказано про хостинг или сборку исходного кода. Две операционные заметки, а не политические. Во-первых, CI-раннер, принимающий рабочие процессы от посторонних, — это бесплатный вычислительный сервис для того, кто его найдёт: стандартное злоупотребление — майнинг криптовалюты через pull request, и его найдут в течение нескольких дней после открытия. Держите регистрацию закрытой и требуйте подтверждения перед запуском рабочих процессов для всех, кто ещё не является участником (collaborator). Во-вторых, если ваши сборки при каждом запуске тянут крупные образы контейнеров — это обычный трафик, и он включён в тариф, но всё равно стоит кешировать их локально, потому что это ускоряет пайплайн и является вежливым по отношению к вышестоящим реестрам, от которых вы зависите.
Собственный фордж на аккаунте, который не носит вашего имени. Без KYC, оплата криптовалютой.
Sentinel — 2 vCPU и 4 ГБ за $3.90 в месяц, в Helsinki, Stockholm, Oslo или Reykjavík. Достаточно для Forgejo, PostgreSQL и обратного прокси, с запасом для репозиториев, которые вы давно собирались перенести. Добавьте второй — для раннера. Без email при регистрации, без документа, удостоверяющего личность, и без индекса на уровне продукта для всего, что вы отправляете push.
Последняя проверка · 2026-10-12 · Источники · Документация администратора Forgejo (установка, Actions, командная строка), сравнение Forgejo с Gitea, страницы об управлении Codeberg e.V., документация PostgreSQL и Caddy, git-config(1) · Периодичность · ежегодно
Anonymous VPS hosting in 2026 — the cluster.
This guide is one spoke of a larger series. The pillar walks the three privacy layers end to end — the sibling spokes below dive into the specifics.
Three independent layers — signup, payment, network — explained, legal context included, common mistakes flagged.
Bitwarden-compatible password vault under your own control.
Files, calendar, contacts, photos — owned, not rented.
Postfix, Dovecot, SPF/DKIM/DMARC — deliverability included.
A meta search engine that does not log you — because you own it.