Хранение исходного кода и конфиденциальных наработок клиентов на зарубежных публичных платформах (GitHub, GitLab SaaS, Bitbucket) в современных реалиях сопряжено с серьезными операционными рисками: внезапные блокировки корпоративных учетных записей, запрет на оплату платных тарифов российскими картами, лимиты на приватные репозитории и угроза утечки коммерческой тайны. Все больше разработчиков, IT-агентств и системных администраторов приходят к необходимости развернуть собственный изолированный Git-сервер на подконтрольном сервере.
Первая мысль большинства инженеров — развернуть собственный GitLab Community Edition (CE). Однако попытка поднять GitLab на типовом недорогом сервере моментально превращается в кошмар: комбайн из Ruby on Rails, Sidekiq, Redis, Puma, Gitaly и Prometheus требует от 4 до 8 ГБ оперативной памяти и утилизирует 2–4 ядра процессора еще до создания первого коммита. На сервере стоимостью до 1000 ₽/мес GitLab либо не запускается вовсе, либо регулярно падает под ударами Linux OOM-Killer.
Идеальное инженерное решение для независимой команды — Gitea. Это сверхбыстрый и функциональный Git-сервер, написанный на языке Go. Он расходует всего 150–200 МБ оперативной памяти, моментально стартует и включает в себя все привычные инструменты: Pull Requests, ветки и релизы, встроенный Docker Registry, канбан-доски, поддержку Git LFS и полноценный движок CI/CD (Gitea Actions), полностью совместимый с синтаксисом GitHub Actions.
В этом практическом руководстве мы разберем развертывание отказоустойчивого стека Gitea в Docker Compose с базой данных PostgreSQL 16, настроим бесшовный SSH на стандартном порту 22 (без неудобных портов вроде 2222), закроем веб-интерфейс за Nginx с автопродлением SSL, защитим регистрацию и подключим локальный раннер автоматической сборки.
1. Почему GitLab перегружает недорогой VDS и преимущества легковесной Gitea
Главное архитектурное отличие между GitLab и Gitea заключается в парадигме разработки и используемом технологическом стеке. GitLab создавался как энтерпрайз-монолит на базе Ruby, требующий десятка параллельно работающих фоновых демонов для маршрутизации задач, веб-интерфейса и парсинга Git-объектов. Gitea скомпилирована в единый монолитный бинарник на Go с минимальным потреблением системных ресурсов.
| Критерий сравнения | GitLab CE (Self-Hosted) | Gitea (Docker Compose) |
|---|---|---|
| Базовый расход RAM | 4–8 ГБ RAM (тяжелый оверхед) |
150–250 МБ RAM (вместе с базой данных) |
| Время холодного старта | 3–6 минут (инициализация демонов) | 1–3 секунды (моментальный запуск) |
| Движок CI/CD | GitLab CI Runner (.gitlab-ci.yml) | Gitea Actions (совместим с GitHub Actions .github/workflows) |
| Реестр пакетов и Docker | Встроенный (требует много RAM) | Встроенный (Docker, npm, PyPI, Maven, Composer) |
| Минимальный бюджет VDS | от 2500–4000 ₽/мес | от 300–600 ₽/мес |
| Простота резервного копирования | Сложные дампы, чувствительные к версиям | Единая команда gitea dump в компактный zip |
💡 Совет инженера:
Хотя Gitea поддерживает работу со встроенной базой SQLite, для постоянной эксплуатации настоятельно рекомендуется использовать PostgreSQL. Встроенная SQLite блокирует базу на запись при одновременных push-операциях или параллельной работе раннеров CI/CD (ошибка database is locked), тогда как PostgreSQL легко выдерживает десятки одновременных потоков разработчиков.
2. Системные требования и сайзинг ресурсов VDS под Git-сервер
Для стабильной работы связки Gitea + PostgreSQL достаточно самого базового тарифа, однако конфигурацию следует выбирать с учетом размера репозиториев и планируемой интенсивности CI/CD:
- Процессор (vCPU): 1–2 виртуальных ядра с высокой тактовой частотой (от 3.0 ГГц). Компиляция Git-объектов при операциях
git pushи упаковка pack-файлов требовательны к производительности ядра. - Оперативная память (RAM): 2 ГБ RAM — комфортный минимум для связки Gitea, PostgreSQL 16 и веб-сервера Nginx. Если вы планируете запускать раннер
act_runnerна том же сервере для компиляции приложений, выбирайте тариф с 4 ГБ RAM. - Накопитель (NVMe SSD): от 30–50 ГБ. Исходный код сжимается эффективно, но хранение истории коммитов, артефактов CI/CD, Git LFS (видео, графика, бинарники) и образов Docker Registry требует быстрого дискового пространства со скоростью чтения от 2000 МБ/с.
- Сетевой интерфейс: выделенный публичный IPv4-адрес со скоростью порта от 100 Мбит/с для быстрой передачи объемных репозиториев.
3. Рекомендуемые VDS-провайдеры для размещения Git-сервера
Для надежного размещения центрального Git-репозитория компании критически важны стабильность сетевых маршрутов, высокая скорость NVMe-дисков и возможность создания автоматических моментальных снимков (снапшотов) диска перед обновлениями. Мы рекомендуем проверенные облачные площадки с дата-центрами в РФ:
Рекомендуемые VDS-провайдеры
Отказоустойчивые сервера с быстрыми NVMe и каналом до 1 Гбит/с
Timeweb Cloud
Отличный вариант для Git-сервера: сверхбыстрые NVMe-диски до 3500 МБ/с, стабильный аптайм 99.98%, мгновенное создание снапшотов диска и удобное управление приватными сетями.
Selectel
Надежная корпоративная инфраструктура в Москве и Санкт-Петербурге: гарантированные vCPU без переподписки, идеальная сетевая связность и встроенное объектное S3-хранилище для бэкапов.
Beget
Быстрое развертывание VDS на базе Ubuntu 24.04 LTS: прозрачные тарифы, отзывчивая техническая поддержка 24/7 и готовые шаблоны Docker из коробки.
4. Развертывание связки Gitea и PostgreSQL 16 в Docker Compose
Для изоляции сервисов и простоты обслуживания мы развернем стек в контейнерах Docker. Подготовим выделенную рабочую директорию на сервере:
sudo mkdir -p /opt/gitea/{data,postgres}
cd /opt/gitea
Создадим пользователя git на хостовой системе. Это необходимо для того, чтобы права на файлы репозиториев внутри томов Docker совпадали с системным пользователем, а также для реализации сквозного доступа по SSH на порту 22:
# Создаем системного пользователя git с UID 1000 и GID 1000
sudo adduser --system --shell /bin/bash --gecos 'Gitea Git Version Control' --group --disabled-password --home /home/git git
# Назначаем права на директорию данных
sudo chown -R git:git /opt/gitea/data
Сгенерируем надежный пароль для базы данных PostgreSQL с помощью системного генератора случайных байтов:
openssl rand -hex 24
Создадим файл окружения /opt/gitea/.env, где зафиксируем ключевые параметры подключения:
POSTGRES_DB=gitea
POSTGRES_USER=gitea
POSTGRES_PASSWORD=ВАШ_НАДЕЖНЫЙ_СГЕНЕРИРОВАННЫЙ_ПАРОЛЬ
GITEA_DOMAIN=git.example.com
GITEA_ROOT_URL=https://git.example.com/
Создадим файл оркестрации /opt/gitea/docker-compose.yml. Обратите внимание на настройку лимитов памяти: директива mem_limit работает во всех версиях Docker Compose standalone (v1 и v2) без необходимости запуска в режиме Swarm, надежно страхуя хост от OOM-Killer. Веб-интерфейс публикуется только на локальный адрес 127.0.0.1:3000 для обратного проксирования через Nginx:
version: '3.8'
networks:
gitea-network:
driver: bridge
services:
db:
image: postgres:16-alpine
container_name: gitea-db
restart: always
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
networks:
- gitea-network
volumes:
- ./postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
mem_limit: 512m
server:
image: gitea/gitea:latest
container_name: gitea-server
restart: always
depends_on:
db:
condition: service_healthy
networks:
- gitea-network
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=db:5432
- GITEA__database__NAME=${POSTGRES_DB}
- GITEA__database__USER=${POSTGRES_USER}
- GITEA__database__PASSWD=${POSTGRES_PASSWORD}
- GITEA__server__DOMAIN=${GITEA_DOMAIN}
- GITEA__server__SSH_DOMAIN=${GITEA_DOMAIN}
- GITEA__server__ROOT_URL=${GITEA_ROOT_URL}
- GITEA__server__HTTP_PORT=3000
- GITEA__server__SSH_PORT=22
- GITEA__server__SSH_LISTEN_PORT=22
- GITEA__server__START_SSH_SERVER=true
- GITEA__server__APP_DATA_PATH=/data
volumes:
- ./data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "127.0.0.1:3000:3000"
# Внутренний SSH-порт контейнера не требует публикации наружу,
# так как сквозной SSH Passthrough работает напрямую через docker exec на хосте.
mem_limit: 1024m
Запустим контейнеры в фоновом режиме:
docker compose up -d
Проверим статус запуска и убедимся, что контейнеры перешли в состояние Up (healthy):
docker compose ps
5. Бесшовный SSH на стандартном порту 22: настройка SSH Passthrough
Типичная проблема стандартных руководств по Gitea в Docker: авторы пробрасывают порт 2222:22 наружу. Из-за этого разработчикам приходится настраивать файл ~/.ssh/config или писать неудобные URL для клонирования вида git clone ssh://git@git.example.com:2222/team/repo.git.
Мы настроим штатный механизм SSH Passthrough. Он оставляет системный SSH администратора на стандартном порту 22, но при подключении пользователя git@git.example.com прозрачно передает Git-команды внутрь контейнера Docker. В результате разработчики используют классический URL git clone git@git.example.com:team/repo.git.
💡 Как устроен механизм сквозного вызова Git SSH:
Когда разработчик добавляет открытый ключ через веб-интерфейс Gitea, Gitea записывает строку в файл authorized_keys со строгой привязкой к внутренней команде: command="/usr/local/bin/gitea --config=/data/gitea/conf/app.ini serv key-1",no-port-forwarding.... При обращении по SSH хостовый OpenSSH находит ключ и запускает команду /usr/local/bin/gitea на хосте. Разместив по этому пути скрипт-прокси (shim), мы перенаправляем переданные аргументы "$@" прямо внутрь контейнера через docker exec.
Создадим символическую ссылку на файл авторизованных ключей, связывая хост и контейнер:
# Создаем папку .ssh для хостового пользователя git
sudo mkdir -p /home/git/.ssh
sudo chmod 700 /home/git/.ssh
# Связываем authorized_keys хоста с хранилищем Gitea внутри data-тома
sudo ln -sf /opt/gitea/data/git/.ssh/authorized_keys /home/git/.ssh/authorized_keys
sudo chown -R git:git /home/git/.ssh
Создадим скрипт-прокси /usr/local/bin/gitea на хостовой системе. Скрипт принимает оригинальную команду SSH_ORIGINAL_COMMAND и все входящие аргументы "$@" (включая serv key-ID), передавая их бинарнику Gitea внутри контейнера:
sudo nano /usr/local/bin/gitea
Вставим следующий скрипт:
#!/bin/sh
# Сквозное перенаправление вызовов Git SSH внутрь Docker-контейнера Gitea
exec /usr/bin/docker exec -i -u 1000:1000 -e SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" gitea-server /usr/local/bin/gitea "$@"
Сделаем скрипт исполняемым:
sudo chmod +x /usr/local/bin/gitea
⚠️ Безопасность пользователя git и Docker-сокета:
Частая ошибка системных администраторов — добавление пользователя git в группу docker (usermod -aG docker git). Членство в группе Docker эквивалентно правам root на сервере (позволяет смонтировать корневую файловую систему хоста). Для безопасного продакшена используйте ограниченное правило в sudoers, разрешающее пользователю git выполнять исключительно команду docker exec для контейнера gitea-server без пароля:
# Создаем изолированное правило безопасности sudoers
echo "git ALL=(ALL) NOPASSWD: /usr/bin/docker exec -i * gitea-server *" | sudo tee /etc/sudoers.d/gitea-docker
sudo chmod 0440 /etc/sudoers.d/gitea-docker
Если используется правило через sudoers, обновите вызов в /usr/local/bin/gitea, добавив sudo перед командой docker exec. Если же сервер полностью изолирован и на нем работает только один сервис, можно использовать стандартное добавление в группу docker: sudo usermod -aG docker git.
Проверим настройки безопасности хостового SSH-демона /etc/ssh/sshd_config. Убедимся, что включена аутентификация по ключам и разрешена обработка персональных файлов authorized_keys:
# Проверка наличия директив в /etc/ssh/sshd_config
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
Перезапустим демон SSH на хосте:
sudo systemctl reload ssh
Теперь при добавлении открытого SSH-ключа в веб-интерфейсе Gitea разработчик сможет мгновенно клонировать репозитории по стандартному протоколу git@git.example.com:user/project.git через порт 22 без указания номеров портов.
6. Настройка Nginx Reverse Proxy и автоматический выпуск сертификатов Let's Encrypt
Gitea слушает веб-запросы на внутреннем порту 127.0.0.1:3000. Чтобы обеспечить шифрование HTTPS, поддержку больших файлов Git LFS и сжатие трафика, настроим обратный прокси на базе веб-сервера Nginx.
Установим Nginx и Certbot (если они еще не установлены на VDS):
sudo apt update && sudo apt install -y nginx certbot python3-certbot-nginx
Создадим виртуальный хост Nginx /etc/nginx/sites-available/gitea.conf. Буферизацию мы отключаем только для длинных потоковых запросов (вебхуки, Git push/pull и стриминг терминала раннеров CI/CD), сохраняя эффективное кэширование статических файлов веб-интерфейса:
server {
listen 80;
server_name git.example.com;
# Максимальный размер загружаемого файла (важно для Git LFS и релизов)
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;
# Поддержка WebSocket для интерактивного терминала и логов CI/CD
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Отключение буферизации для плавного стриминга вывода раннеров и Git-пакетов
proxy_buffering off;
proxy_request_buffering off;
proxy_read_timeout 600s;
}
}
Активируем конфигурацию и проверим корректность синтаксиса:
sudo ln -s /etc/nginx/sites-available/gitea.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Выпустим надежный бесплатный SSL-сертификат Let's Encrypt с обязательным указанием флага --redirect для автоматической настройки принудительного перенаправления с незащищенного HTTP на HTTPS:
sudo certbot --nginx -d git.example.com --redirect
7. Тонкая настройка app.ini: отключение публичной регистрации и харденинг
После первого входа в веб-интерфейс https://git.example.com и создания учетной записи администратора необходимо зафиксировать настройки безопасности в основном конфигурационном файле /opt/gitea/data/gitea/conf/app.ini.
⚠️ Важное предупреждение о безопасности:
По умолчанию Gitea разрешает свободную регистрацию любому пользователю интернета. Если не отключить регистрацию, сервер быстро обнаружат спам-боты, создадут тысячи учетных записей и заполнят дисковое пространство VDS мусорными репозиториями и майнерами.
Откроем файл конфигурации для редактирования:
sudo nano /opt/gitea/data/gitea/conf/app.ini
Найдем или добавим в соответствующие секции критические параметры безопасности. Обратите внимание на директиву NO_REPLY_ADDRESS — она обязательно должна содержать символ @ для валидного формирования скрытых email-адресов разработчиков:
[service]
; Запрет свободной регистрации посторонних пользователей
DISABLE_REGISTRATION = true
; Скрытие содержимого репозиториев от неавторизованных посетителей
REQUIRE_SIGNIN_VIEW = true
; Служебный email адрес для анонимизации коммитов (обязательно с символом @)
NO_REPLY_ADDRESS = noreply@git.example.com
; Автоматическое подтверждение аккаунтов администратором
REGISTER_MANUAL_CONFIRM = true
[security]
INSTALL_LOCK = true
SECRET_KEY = ВАШ_СЕКРЕТНЫЙ_КЛЮЧ
INTERNAL_TOKEN = ВАШ_ВНУТРЕННИЙ_ТОКЕН
; Требования к надежности паролей (буквы разного регистра, цифры и спецсимволы)
PASSWORD_COMPLEXITY = lower,upper,digit,spec
MIN_PASSWORD_LENGTH = 10
[server]
SSH_PORT = 22
SSH_LISTEN_PORT = 22
LFS_START_SERVER = true
LFS_JWT_SECRET = ВАШ_LFS_СЕКРЕТ
OFFLINE_MODE = true
Перезапустим контейнер Gitea, чтобы применить обновленную конфигурацию:
cd /opt/gitea && docker compose restart server
8. Подключение раннера Gitea Actions (act_runner) для локального CI/CD
Gitea включает встроенную систему непрерывной интеграции Gitea Actions, полностью совместимую с экосистемой GitHub Actions. Для выполнения пайплайнов (сборки образов, тестирования кода, деплоя) используется легковесный демон act_runner, запускающий задачи в изолированных Docker-контейнерах.
Сначала активируем поддержку Actions в конфигурационном файле /opt/gitea/data/gitea/conf/app.ini:
[actions]
ENABLED = true
Перезапустим Gitea: docker compose restart server.
Получим токен регистрации раннера:
- Откройте веб-панель администратора: Панель управления Site Administration → Actions → Runners.
- Нажмите кнопку «Создать раннер» (Create new Runner) и скопируйте сгенерированный токен регистрации
REGISTRATION_TOKEN.
Создадим директорию для раннера /opt/gitea-runner:
sudo mkdir -p /opt/gitea-runner
cd /opt/gitea-runner
Создадим файл оркестрации раннера /opt/gitea-runner/docker-compose.yml. В метках GITEA_RUNNER_LABELS мы используем стандартный полноценный образ catthehacker/ubuntu:act-latest, в который уже встроены git, curl, jq, node и базовые утилиты, необходимые для безотказной работы экшена actions/checkout:
version: '3.8'
services:
runner:
image: gitea/act_runner:latest
container_name: gitea-act-runner
restart: always
environment:
- GITEA_INSTANCE_URL=https://git.example.com
- GITEA_RUNNER_REGISTRATION_TOKEN=СКОПИРОВАННЫЙ_ТОКЕН_РЕГИСТРАЦИИ
- GITEA_RUNNER_NAME=vds-production-runner
- GITEA_RUNNER_LABELS=ubuntu-latest:docker://catthehacker/ubuntu:act-latest,ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04
volumes:
- ./data:/data
- /var/run/docker.sock:/var/run/docker.sock
mem_limit: 1536m
Запустим раннер:
docker compose up -d
В веб-панели Gitea статус раннера сменится на зеленый индикатор «Idle» (готов к приему задач). Теперь в любом репозитории вы можете создать файл .gitea/workflows/build.yaml, и сборка запустится автоматически при каждом коммите:
name: Production Build & Test
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Run Tests
run: |
echo "Running automated testing inside isolated runner..."
node -v
npm test || echo "Tests passed!"
9. Автоматическое резервное копирование репозиториев и базы данных в S3
Git-сервер — это критически важный узел инфраструктуры, потеря которого означает утрату месяцев работы команды. Настроим автоматическое ночное резервное копирование базы данных PostgreSQL и репозиториев Gitea с выгрузкой архива во внешнее S3-совместимое хранилище (Selectel S3, Timeweb Cloud S3 или собственный MinIO).
Gitea имеет встроенную CLI-утилиту горячего бэкапа gitea dump. При выполнении команды внутри контейнера утилита автоматически подключается к базе данных PostgreSQL по внутренней сети Docker (хост db:5432 из app.ini) и упаковывает в единый защищенный zip-архив все репозитории, дамп таблиц базы, конфигурационный файл и ключи шифрования.
Создадим скрипт бэкапа /opt/gitea/backup.sh:
sudo nano /opt/gitea/backup.sh
Вставим следующий скрипт:
#!/bin/bash
set -e
BACKUP_DIR="/tmp/gitea-backups"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
FILENAME="gitea_backup_${TIMESTAMP}.zip"
S3_BUCKET="s3://my-company-backups/gitea"
mkdir -p "${BACKUP_DIR}"
echo "[INFO] Создание полного дампа Gitea (БД PostgreSQL + репозитории)..."
# Выполняем дамп внутри контейнера: Gitea сама обращается к PostgreSQL по внутренней сети Docker
docker exec -u 1000:1000 -w /tmp gitea-server gitea dump -c /data/gitea/conf/app.ini -f "/tmp/${FILENAME}"
# Копируем созданный архив из контейнера на хост
docker cp "gitea-server:/tmp/${FILENAME}" "${BACKUP_DIR}/${FILENAME}"
docker exec gitea-server rm -f "/tmp/${FILENAME}"
echo "[INFO] Выгрузка бэкапа в S3 хранилище..."
# Выгрузка через AWS CLI (предварительно настроенные ключи)
aws s3 cp "${BACKUP_DIR}/${FILENAME}" "${S3_BUCKET}/${FILENAME}" --endpoint-url https://s3.timeweb.cloud
echo "[INFO] Очистка локальных временных файлов..."
rm -f "${BACKUP_DIR}/${FILENAME}"
echo "[INFO] Резервное копирование успешно завершено!"
Сделаем скрипт исполняемым:
sudo chmod +x /opt/gitea/backup.sh
Добавим задание в системный планировщик crontab для выполнения резервного копирования каждую ночь в 03:30:
sudo crontab -e
30 3 * * * /opt/gitea/backup.sh > /var/log/gitea-backup.log 2>&1
10. Резюме и чек-лист готовности к промышленной эксплуатации
Развертывание собственного сервера Gitea на VDS решает главные проблемы современной разработки: гарантирует 100% суверенитет и защиту интеллектуальной собственности, избавляет команду от риска блокировок зарубежными сервисами и экономит бюджет за счет бережного расхода аппаратных ресурсов.
Преимущества стека Gitea + PostgreSQL
- Минимальный расход памяти: всего 150–200 МБ RAM вместо 4+ ГБ у GitLab.
- Сквозной SSH на стандартном порту 22 без изменения рабочих URL разработчиков.
- Встроенный раннер CI/CD, совместимый с привычным синтаксисом GitHub Actions.
- Встроенные реестры пакетов (Docker Container Registry, npm, PyPI, Maven).
- Быстрый и надежный дамп всей системы одной командой.
Точки контроля администратора
- Обязательное отключение публичной регистрации (
DISABLE_REGISTRATION = true). - Необходимость настройки регулярного экспорта бэкапов в удаленное S3-хранилище.
- Контроль свободного места на NVMe при активном использовании Git LFS и сборок CI/CD.
- Изоляция служебных портов базы данных (5432) и внутреннего веба (3000) от публичной сети.
Разверните собственный независимый Git-сервер на надежном VDS
Выберите производительный виртуальный сервер на быстрых NVMe-дисках у проверенных провайдеров для стабильной работы репозиториев команды 24/7 без абонентской платы и риска блокировок.