- Python 64.7%
- Shell 35.3%
|
|
||
|---|---|---|
| runner | ||
| scripts | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| README.md | ||
Forgejo self-hosted на VPS — комплект деплоя (git.fan-lab.ru)
Готовый набор файлов для развёртывания self-hosted git-forge Forgejo с PostgreSQL и CI-раннером (forgejo-runner). Рассчитан на VPS Ubuntu 22.04/24.04 (2 vCPU / 4 GB RAM / 60 GB NVMe) и домен git.fan-lab.ru. HTTPS терминируется на существующем nginx хоста (сервер под ISPmanager) — фронта в docker-стеке нет.
Папка копируется на сервер в /opt/forgejo-deploy; все команды ниже предполагают,
что вы находитесь в этой папке, если не указано иное.
Что внутри
| Сервис | Образ | Роль | mem_limit |
|---|---|---|---|
forgejo |
codeberg.org/forgejo/forgejo:15 |
git-форж, web UI, SSH для git на порту 2222 | 1g |
db |
postgres:17-alpine |
База данных PostgreSQL | 512m |
runner |
code.forgejo.org/forgejo/runner:13 |
CI-раннер (Forgejo Actions) | 2g |
Фронт (реверс-прокси + TLS) — nginx на хосте (существующий), вне compose-стека.
Схема (текстом):
интернет
│ 80/443 TCP
▼
[ nginx на хосте ] ← существующий фронт (ISPmanager),
│ TLS-сертификат через certbot (webroot)
│ proxy_pass http://127.0.0.1:3000
▼
[ Forgejo ] ← docker, HTTP опубликован только на loopback;
git по SSH: порт хоста 2222 → контейнер :22
│ внутренняя сеть (internal: true)
▼
[ PostgreSQL ] — портов наружу нет, доступен только forgejo
[ runner ] ── /var/run/docker.sock ── запускает CI-контейнеры на хосте
(labels: node:20-bookworm; ubuntu-latest = catthehacker/ubuntu:act-latest)
- PostgreSQL не имеет опубликованных портов и живёт во внутренней сети — снаружи недоступен.
- Секреты — только через
.env(его нет в git, см..gitignore);runner/.runner(файл регистрации раннера с токеном) тоже игнорируется git'ом. - Имя compose-проекта зафиксировано (
name: forgejo-deploy), поэтому имена томов стабильны и используются вscripts/backup.sh.
Требования
- VPS: Ubuntu 22.04 или 24.04, 2 vCPU / 4 GB RAM / 60 GB NVMe.
- Домен
fan-lab.ru, от которого будет использоваться поддоменgit.fan-lab.ru. - Root-доступ (sudo). Системный sshd остаётся на порту 22 — git-SSH будет на 2222.
- Свободный порт 2222 (80/443 уже заняты nginx хоста — это норма: контейнеры эти порты не слушают).
DNS — ДО старта
Перед выпуском сертификата certbot'ом DNS уже должен работать — иначе
сертификат Let's Encrypt для git.fan-lab.ru не выпустится и HTTPS не заработает:
| Тип | Имя | Значение |
|---|---|---|
| A | git (git.fan-lab.ru) |
IP сервера |
Записи @ и остальных поддоменов (fan-lab.ru, fan.sea-agents.ru,
agent.sea-agents.ru) уже обслуживают существующие сайты — не трогайте их.
Проверка (должен вернуться IP сервера):
dig +short fan-lab.ru
dig +short git.fan-lab.ru
Установка Docker
curl -fsSL https://get.docker.com | sh
sudo systemctl enable --now docker
sudo usermod -aG docker $USER # перелогиньтесь, чтобы группа применилась
Если Docker Hub плохо доступен с VPS (актуально для РФ)
Образы Forgejo и раннера тянутся с codeberg.org (обычно доступен без проблем),
а вот postgres, alpine и CI-образы (node, catthehacker/ubuntu)
берутся из Docker Hub. Если пулы зависают или падают по таймаутам — добавьте
зеркало Docker Hub в /etc/docker/daemon.json, например:
sudo tee /etc/docker/daemon.json <<'EOF'
{
"registry-mirrors": [
"https://dockerhub.timeweb.cloud",
"https://mirror.gcr.io"
]
}
EOF
sudo systemctl restart docker
Зеркало (mirror.gcr.io — публичное зеркало от Google и т.п.) выбирайте актуальное
на момент настройки — состав рабочих публичных зеркал со временем меняется.
Проверить, что пул теперь работает: docker pull postgres:17-alpine.
Версии образов
Все образы запинены на мажорные версии: forgejo:15, postgres:17-alpine,
forgejo/runner:13 (code.forgejo.org).
ℹ️ Образы Forgejo и раннера берутся с
codeberg.org/code.forgejo.org, PostgreSQL — с Docker Hub (через зеркала, см. выше). Актуальность тега раннера (13= v13.1.0 на момент сборки комплекта) можно сверить с https://code.forgejo.org/forgejo/runner/releases https://codeberg.org/forgejo/runner/releases — если текущий мажор другой, поправьтеimage:у сервисаrunnerвdocker-compose.yml.
Копирование папки на сервер
С локальной машины (PowerShell):
scp -r C:\Users\User\forgejo-deploy root@IP_СЕРВЕРА:/opt/forgejo-deploy
или rsync (например, из WSL): rsync -av --exclude .git forgejo-deploy/ root@IP_СЕРВЕРА:/opt/forgejo-deploy/.
Если под /opt нет прав — скопируйте в домашнюю папку и переместите через sudo mv.
Так как файлы идут с Windows, верните исполняемый бит скрипту бэкапа:
cd /opt/forgejo-deploy
chmod +x scripts/backup.sh
.env — пароль базы
cd /opt/forgejo-deploy
cp .env.example .env
chmod 600 .env
openssl rand -base64 24 # сгенерировать пароль
nano .env # вставить его в POSTGRES_PASSWORD
Пароль нужно выставить до первого docker compose up -d (смена пароля после
установки потребует ещё ALTER USER внутри PostgreSQL). Файл .env добавлен
в .gitignore и в git не попадает.
Firewall (ufw)
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 2222/tcp
sudo ufw enable
sudo ufw status
- 22 — системный sshd (доступ администратора);
- 80/443 — nginx на хосте (обычно уже открыто — через них работают существующие сайты); сами контейнеры эти порты не публикуют;
- 2222 — git по SSH (sshd внутри forgejo).
Swap (рекомендуется при 4 GB RAM)
Полезно для Gradle/Android-сборок на раннере:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -h
Запуск
cd /opt/forgejo-deploy
docker compose up -d
docker compose ps
docker compose logs -f forgejo # выйти: Ctrl+C
До регистрации раннера контейнер runner будет периодически падать и
перезапускаться (нет файла регистрации) — это нормально, см. раздел
«Регистрация раннера» ниже.
Реверс-прокси: nginx на хосте
Фронт для git.fan-lab.ru — уже существующий nginx на хосте (ставится
ISPmanager'ом, он же обслуживает остальные сайты сервера: fan-lab.ru,
fan.sea-agents.ru, agent.sea-agents.ru). Docker-стек фронта не содержит:
Forgejo публикует HTTP только на loopback хоста (127.0.0.1:3000).
⚠️ Важно при работе с ISPmanager:
- nginx на хосте один и он уже слушает 80/443 — не маскируйте его и не пытайтесь запустить второй фронт (отдельный nginx, другой реверс-прокси): получите
port 80 already in use;- vhost'ы существующих сайтов лежат в
/etc/nginx/vhosts/www-root/и управляются панелью — не трогайте их;- наш vhost — отдельный файл
/etc/nginx/conf.d/git-fan-lab.conf, панель его не обслуживает и не перезатрёт.
Фаза 1 — HTTP-only vhost (пока без сертификата)
sudo tee /etc/nginx/conf.d/git-fan-lab.conf > /dev/null <<'EOF'
server {
listen 80;
server_name git.fan-lab.ru;
# webroot для certbot (каталог создаём следующей командой)
location ^~ /.well-known/acme-challenge/ {
root /var/www/letsencrypt-git;
}
location / {
return 301 https://$host$request_uri;
}
}
EOF
sudo mkdir -p /var/www/letsencrypt-git
sudo nginx -t && sudo systemctl reload nginx
Пока сертификата нет, редирект на HTTPS ведёт в никуда — это ожидаемое промежуточное состояние.
Выпуск сертификата (certbot, webroot)
sudo certbot certonly --webroot -w /var/www/letsencrypt-git \
-d git.fan-lab.ru \
--email ТВОЙ@email --agree-tos --no-eff-email
Сертификат появится в /etc/letsencrypt/live/git.fan-lab.ru/.
Фаза 2 — полный vhost с HTTPS
sudo tee /etc/nginx/conf.d/git-fan-lab.conf > /dev/null <<'EOF'
server {
listen 80;
server_name git.fan-lab.ru;
location ^~ /.well-known/acme-challenge/ {
root /var/www/letsencrypt-git;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name git.fan-lab.ru;
ssl_certificate /etc/letsencrypt/live/git.fan-lab.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/git.fan-lab.ru/privkey.pem;
# Обязательно: дефолтный лимит nginx 1m ломает git push по HTTP
client_max_body_size 512m;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
EOF
sudo nginx -t && sudo systemctl reload nginx
Откройте https://git.fan-lab.ru — должен открыться интерфейс Forgejo
(мастер начальной установки, см. следующий раздел).
Автопродление сертификата
Certbot продлевает сертификат сам (systemd timer), но после обновления nginx нужно перечитать сертификаты — вешаем renewal hook:
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh > /dev/null <<'EOF'
#!/bin/sh
systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
# проверка, что продление отрабатывает
sudo certbot renew --dry-run
Первый запуск и настройка Forgejo
- После настройки nginx vhost и certbot (см. «Реверс-прокси» выше) откройте
https://git.fan-lab.ru— откроется мастер начальной настройки:- База данных: тип
PostgreSQL, хостdb:5432, пользовательforgejo, пароль — из.env, базаforgejo; - Домен сервера:
git.fan-lab.ru; - Base URL:
https://git.fan-lab.ru/; - Порт SSH-сервера:
2222. Большинство полей уже подставлено из переменных окружения compose. Нажмите «Установить Forgejo».
- База данных: тип
- Первый зарегистрированный пользователь становится администратором — зарегистрируйтесь сразу после установки.
- Отключите открытую регистрацию: Site Administration → настройки
(либо в
app.ini— файл/data/gitea/conf/app.iniвнутри томаforgejo-data, параметрDISABLE_REGISTRATION = trueв секции[service], затемdocker compose restart forgejo). - Включите двухфакторную аутентификацию: аватар → Settings → Security.
Регистрация раннера
- В веб-интерфейсе: Site Administration → Actions → Runners → «Create runner token».
- Зарегистрируйте раннер:
cd /opt/forgejo-deploy
docker compose run --rm runner forgejo-runner register \
--instance https://git.fan-lab.ru \
--token <TOKEN> \
--name vps-runner \
--config /etc/runner/config.yaml
Результат пишется в runner/.runner (на хосте; в git не попадает).
3. Запустите раннер: docker compose up -d runner
4. Проверьте в UI (Site Administration → Actions → Runners), что vps-runner в статусе online.
Проверка CI
В любом репозитории создайте файл .forgejo/workflows/ci.yml:
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
- name: Hello
run: echo "CI works on Forgejo"
После git push во вкладке «Actions» появится запуск. Первый запуск небыстрый —
тянется job-образ catthehacker/ubuntu:act-latest (соответствие labels → образы
задано в runner/config.yaml; образы job-контейнеров берутся из Docker Hub).
Бэкапы
Разово:
sudo /opt/forgejo-deploy/scripts/backup.sh
Скрипт кладёт в /var/backups/forgejo два файла: forgejo-db-<штамп>.sql.gz
(логический дамп PostgreSQL) и forgejo-data-<штамп>.tar.gz (том forgejo-data:
git-репозитории, LFS, вложения, аватары). Файлы старше 7 дней удаляются.
Cron (под root): sudo crontab -e, строка:
0 4 * * * /opt/forgejo-deploy/scripts/backup.sh >> /var/log/forgejo-backup.log 2>&1
Держите копию бэкапов вне VPS — например, rclone или restic на другой
сервер/хранилище: бэкап на том же диске не спасёт при потере диска или VPS.
Обновление
cd /opt/forgejo-deploy
docker compose pull
docker compose up -d
Перед мажорными апгрейдами (например, 15 → 16) — сначала бэкап и чтение release notes: https://codeberg.org/forgejo/forgejo/releases.
Восстановление из бэкапа
<штамп> — метка времени из имени файлов бэкапа. Имя тома
forgejo-deploy_forgejo-data соответствует имени проекта, зафиксированному в compose.
cd /opt/forgejo-deploy
docker compose down
# 1) база: пересоздать и накатить дамп
docker compose up -d db
until docker compose exec -T db pg_isready -U forgejo >/dev/null 2>&1; do sleep 1; done
docker compose exec -T db psql -U forgejo -d postgres \
-c 'DROP DATABASE IF EXISTS forgejo;' -c 'CREATE DATABASE forgejo OWNER forgejo;'
gunzip -c /var/backups/forgejo/forgejo-db-<штамп>.sql.gz \
| docker compose exec -T db psql -U forgejo -d forgejo
# 2) том forgejo-data
docker run --rm \
-v forgejo-deploy_forgejo-data:/data \
-v /var/backups/forgejo:/backup \
alpine:3.20 \
sh -c 'rm -rf /data/* /data/.[!.]* /data/..?* && tar -xzf /backup/forgejo-data-<штамп>.tar.gz -C /data'
# 3) старт
docker compose up -d
Лимиты памяти
Задаются через mem_limit в docker-compose.yml: forgejo — 1g, db — 512m,
runner — 2g. После правок примените: docker compose up -d.
Важно: лимит раннера ограничивает только сам процесс forgejo-runner. CI-контейнеры он запускает через docker-демон хоста, поэтому они потребляют память сервера поверх лимитов сервисов — на 4 GB RAM без swap при Gradle-сборках легко получить OOM (см. раздел про swap выше).
Troubleshooting
address already in use/port 80 already in useприdocker compose up— 80/443 на этом сервере принадлежат nginx хоста; ошибка означает, что порт пытается занять кто-то из стека — либо в нём остался Caddy от старой схемы, либо конфликт с nginx. Для этой схемы Caddy быть не должно: проверьтеdocker compose config(вservices:только forgejo, db, runner); если контейнер Caddy остался от старого деплоя —docker compose down --remove-orphansи поднимите стек заново. Кто слушает 80-й порт:sudo ss -ltnp | grep ':80'(должен быть nginx).