Деплой своего репозитория на основе forejio
  • Python 64.7%
  • Shell 35.3%
Find a file
2026-09-12 16:29:25 +03:00
runner Созданы правила и скрипты создания собственного репозитория 2026-09-12 16:23:00 +03:00
scripts Созданы правила и скрипты создания собственного репозитория 2026-09-12 16:23:00 +03:00
.env.example Созданы правила и скрипты создания собственного репозитория 2026-09-12 16:23:00 +03:00
.gitignore Созданы правила и скрипты создания собственного репозитория 2026-09-12 16:23:00 +03:00
docker-compose.yml Созданы правила и скрипты создания собственного репозитория 2026-09-12 16:23:00 +03:00
README.md first commit 2026-09-12 16:04:01 +03:00

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

  1. После настройки 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».
  2. Первый зарегистрированный пользователь становится администратором — зарегистрируйтесь сразу после установки.
  3. Отключите открытую регистрацию: Site Administration → настройки (либо в app.ini — файл /data/gitea/conf/app.ini внутри тома forgejo-data, параметр DISABLE_REGISTRATION = true в секции [service], затем docker compose restart forgejo).
  4. Включите двухфакторную аутентификацию: аватар → Settings → Security.

Регистрация раннера

  1. В веб-интерфейсе: Site Administration → Actions → Runners → «Create runner token».
  2. Зарегистрируйте раннер:
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 configservices: только forgejo, db, runner); если контейнер Caddy остался от старого деплоя — docker compose down --remove-orphans и поднимите стек заново. Кто слушает 80-й порт: sudo ss -ltnp | grep ':80' (должен быть nginx).