Gitea в Docker: свой Git-сервер на VPS с SSH, бэкапами и обновлением

Запуск Gitea в Docker Compose, первоначальная настройка, SSH на порту 2222, закрытая регистрация, добавление коллег, скрипт резервных копий с ключами SSH, восстановление и обновление. Debian 12 и Ubuntu 24.04.

19 октября 2026 г.·7 мин чтения·gitea, git, docker

Свой Git-сервер нужен, когда код нельзя отдавать стороннему сервису или проекту нужны собственные правила доступа. Gitea — лёгкая открытая программа, которая превращает VPS в такой сервер: репозитории, веб-интерфейс, учётные записи и права, задачи и pull request’ы. На сервере она занимает 100–160 МБ памяти, а для небольшой команды хватает встроенной базы SQLite.

Как устроен Git-сервер на Gitea

К Gitea обращаются двумя способами. По HTTP на порт 3000 — каждая операция требует логин и пароль, и пока адрес начинается с http://, они уходят по сети открытым текстом (заголовок Authorization: Basic виден в tcpdump). По SSH — вы один раз добавляете публичный ключ в профиль, и дальше Git подтверждает личность ключом. Для этого внутри контейнера работает собственный SSH-сервер Gitea на порту 22. Порт 22 самого VPS занят вашим входом, поэтому контейнерный порт мы опубликуем как 2222.

Всё, что меняется в работе, лежит в одной папке /opt/gitea/data: app.ini, база gitea.db, репозитории в git/repositories и ключи SSH-сервера в ssh. Контейнер ничего важного не хранит — его можно удалить и пересоздать, обновление сводится к смене образа, а бэкап — к сохранению этой папки.

Версия 28, а не 1.27. Последним выпуском старой нумерации был 1.27.3, следующий вышел как 28.0.0. В статье используется 28.2.0.

Что нужно

  • Сервер на tanaka.host с Debian 12 или Ubuntu 24.04. Образ около 260 МБ, память 100–160 МБ, место под репозитории — по вашим проектам.
  • Docker и Compose — см. установку Docker.
  • Свободные порты 3000 и 2222.
  • Git на вашем компьютере.

1. Запустите Gitea

mkdir -p /opt/gitea
cd /opt/gitea
nano docker-compose.yml
services:
  gitea:
    image: docker.gitea.com/gitea:28.2.0
    container_name: gitea
    restart: always
    environment:
      - USER_UID=1000
      - USER_GID=1000
    volumes:
      - ./data:/data
    ports:
      - "3000:3000"
      - "2222:22"

Версия образа записана явно — обновление произойдёт, когда вы сами этого захотите. USER_UID/USER_GID — пользователь, от которого Gitea работает внутри контейнера. ./data:/data — папка с базой, репозиториями и настройками рядом с файлом. restart: always поднимает контейнер после перезагрузки сервера.

docker compose up -d
docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}"

В списке должна появиться строка gitea со статусом Up.

Порты открыты без UFW. Docker публикует порты собственными правилами раньше правил UFW, поэтому 3000 и 2222 доступны из интернета сразу. Как спрятать веб-порт — в конце статьи.

2. Пройдите первоначальную настройку

Не оставляйте страницу настройки открытой. Пока вы не нажали кнопку установки, её может заполнить любой, кто знает адрес и порт, — вплоть до создания администратора. Делайте это сразу после запуска.

Откройте http://IP-адрес-сервера:3000. Большинство полей уже заполнено, править нужно несколько:

  • Настройки базы данных — оставьте SQLite3 и путь /data/gitea/gitea.db.
  • Порт SSH сервера — замените 22 на 2222. Так Gitea будет показывать в адресах клонирования тот порт, что опубликован наружу.
  • Gitea Website URL — проверьте, что там адрес, по которому вы открыли страницу.
  • Отметьте Only administrators can create user accounts (no self-registration).
  • Require sign-in to view pages — прячет список репозиториев от гостей; пригодится, если сервер только для своих.

Внизу раскройте Расширенные настройки → Настройки учётной записи администратора: логин, почта, пароль. Письма Gitea без настроенной почты не шлёт, ящик может быть любым. Пароль выбирайте длинный — установщик принимает даже трёхсимвольный и от слабого не защитит. Нажмите Установить Gitea.

Настройки запишутся в /opt/gitea/data/gitea/conf/app.ini.

3. Укажите адрес сервера в app.ini

На странице настройки нет поля с доменом, поэтому Gitea записала DOMAIN = localhost и SSH_DOMAIN = localhost. В результате SSH-адрес репозитория выглядит как ssh://git@localhost:2222/…, и подключиться по нему с другого компьютера нельзя.

cd /opt/gitea
nano data/gitea/conf/app.ini

В блоке [server] замените localhost на IP сервера в строках DOMAIN и SSH_DOMAIN. SSH_PORT = 2222 и SSH_LISTEN_PORT = 22 не трогайте: первая — порт в адресах, вторая — порт внутри контейнера. Проверьте и перезапустите:

grep -n -E "DOMAIN|ROOT_URL|SSH_PORT|SSH_LISTEN" data/gitea/conf/app.ini
docker restart gitea

Когда появится домен, в эти же строки и в ROOT_URL впишете его.

4. Создайте репозиторий

Значок + в правом верхнем углу → Новый репозиторий. Название my-project, отметьте Сделать репозиторий приватным (по умолчанию галочки нет) и Инициализировать репозиторий — Gitea сразу создаст первый коммит с README. На странице репозитория кнопка Код покажет адреса для клонирования по HTTP и SSH.

5. Клонируйте по HTTP

Один раз на своём компьютере представьтесь Git:

git config --global user.name "Ваше Имя"
git config --global user.email "you@example.com"

Репозиторий приватный, Git спросит логин и пароль администратора:

# Клонируем репозиторий по HTTP
git clone http://IP-адрес-сервера:3000/gitadmin/my-project.git
cd my-project

# Добавляем файл и фиксируем изменение
echo "print(1)" > hello.py
git add hello.py
git commit -m "Add hello.py"

# Отправляем коммит на сервер
git push

Пароль по HTTP не защищён. Адрес http://…:3000 не шифрует ни пароль, ни код. Для реальных проектов используйте SSH из следующего шага или поставьте перед Gitea домен с HTTPS.

6. Подключите SSH на порту 2222

Если ключа ещё нет, создайте — подробно в статье про вход по SSH-ключу:

ssh-keygen -t ed25519 -C "me@example.com"
cat ~/.ssh/id_ed25519.pub

В Gitea: меню профиля → Настройки → Ключи SSH / GPG → Добавить ключ. В поле Содержимое вставьте всю строку от ssh-ed25519 до почты. Вставляется только .pub, закрытый ключ никуда не копируйте.

Вернитесь к репозиторию, в окне Код откройте вкладку SSH — адрес начинается с ssh://git@ и содержит порт 2222:

# Клонируем по SSH, порт 2222 входит в адрес
git clone ssh://git@IP-адрес-сервера:2222/gitadmin/my-project.git my-project-ssh
cd my-project-ssh

# Меняем файл и отправляем изменение
echo "SSH works" >> README.md
git commit -am "Update README"
git push

При первом подключении SSH спросит, доверять ли ключу сервера, — ответьте yes. Пароль не спрашивается. Быстро проверить ключ без репозитория: ssh -p 2222 git@IP-адрес-сервера ответит Hi there, gitadmin! You've successfully authenticated… и сообщит, что shell не даёт.

7. Добавьте коллегу

Откройте http://IP-адрес-сервера:3000/user/sign_up без входа — должно быть «возможность регистрации отключена». Если галочку пропустили, в app.ini в блоке [service] задайте DISABLE_REGISTRATION = true и перезапустите контейнер. Там же включается REQUIRE_SIGNIN_VIEW = true.

Учётные записи создаёт администратор: меню профиля → Панель управления → Идентификация и доступ → Пользователи → Создать новый аккаунт. Оставьте включённым Требовать смену пароля пользователем. Коллеге нужно один раз войти через браузер и сменить пароль — до этого Git по HTTP отвечает:

remote: Update your password
fatal: Authentication failed for 'http://IP-адрес-сервера:3000/gitadmin/my-project.git/'

Приватный репозиторий новый пользователь не видит. В репозитории: меню с тремя точками → Настройки → Соавторы → добавьте его, доступ по умолчанию — Запись.

Резервные копии

Официальная команда gitea dump собирает один архив с базой, репозиториями и настройками. Ключей SSH-сервера в нём нет: восстановленный на чистом контейнере сервер получит другие ключи, и у всех, кто уже подключался, SSH начнёт ругаться на их смену (см. Remote host identification has changed). Поэтому скрипт сохраняет папку ssh отдельно.

nano /opt/gitea/backup.sh
#!/bin/sh
# Резервная копия Gitea: официальный gitea dump и ключи SSH-сервера
set -e
cd /opt/gitea
STAMP=$(date +%F-%H%M)
mkdir -p backups

# Архив с базой, репозиториями и настройками, из папки /tmp внутри контейнера
docker exec -u git -w /tmp gitea gitea dump -q -c /data/gitea/conf/app.ini -f /tmp/gitea-dump.zip
docker cp -q gitea:/tmp/gitea-dump.zip backups/gitea-dump-$STAMP.zip
docker exec gitea rm /tmp/gitea-dump.zip

# Ключи SSH-сервера в архив не входят, сохраняем их отдельно
tar czf backups/gitea-ssh-$STAMP.tar.gz -C data ssh

# Храним 7 последних копий
ls -1t backups/gitea-dump-*.zip | tail -n +8 | xargs -r rm
ls -1t backups/gitea-ssh-*.tar.gz | tail -n +8 | xargs -r rm

-u git запускает команду от пользователя Gitea, -w /tmp задаёт рабочую папку, чтобы не получить ошибок прав. Документация советует останавливать сервис на время копирования; для небольшой команды хватит ночного запуска на работающем контейнере.

chmod +x /opt/gitea/backup.sh
/opt/gitea/backup.sh
ls -lh /opt/gitea/backups

В crontab -e:

0 3 * * * /opt/gitea/backup.sh

Папку backups периодически забирайте с сервера — см. резервные копии VPS.

Восстановление из копии

На новом сервере установите Docker и создайте docker-compose.yml из шага 1. Ключи SSH распакуйте до первого запуска — тогда отпечаток сервера совпадёт со старым:

mkdir -p /opt/gitea/data
tar xzf gitea-ssh-ДАТА.tar.gz -C /opt/gitea/data
cd /opt/gitea
docker compose up -d

Скопируйте архив в контейнер и распакуйте на место (cp -a, а не mv, потому что папка conf уже существует):

docker cp gitea-dump-ДАТА.zip gitea:/tmp/gitea-dump.zip
docker exec gitea sh -c "cd /tmp && unzip -q gitea-dump.zip -d restore && cp -a restore/data/. /data/gitea/ && cp -a restore/repos/. /data/git/repositories/ && chown -R git:git /data"

Пересоздайте хуки Git и перезапустите:

docker exec -u git gitea gitea -c /data/gitea/conf/app.ini admin regenerate hooks
docker restart gitea

Обновление

При смене версии Gitea меняет структуру базы, и вернуться назад без копии нельзя. Поэтому сначала бэкап, потом правка версии в image:

cd /opt/gitea
./backup.sh
nano docker-compose.yml
docker compose pull
docker compose up -d

Перед обновлением загляните в список изменений на странице релизов — там отмечены несовместимые правки.

Домен и HTTPS

Когда понадобится нормальный адрес, поставьте перед Gitea nginx с сертификатом, а в app.ini в блоке [server] впишите домен в ROOT_URL, DOMAIN и SSH_DOMAIN. Чтобы веб-порт был виден только через nginx, в Compose замените "3000:3000" на:

      - "127.0.0.1:3000:3000"

После docker compose up -d порт 3000 снаружи перестанет отвечать. Порт 2222 остаётся открытым — он нужен для Git по SSH.

Полезные команды

docker compose ps                 # состояние контейнера, выполнять в /opt/gitea
docker logs gitea                 # журнал Gitea
docker restart gitea              # перезапустить Gitea
docker compose down               # удалить контейнер, данные в ./data останутся

# Список пользователей
docker exec -u git gitea gitea admin user list

# Новый пароль пользователю, например при потере пароля администратора
docker exec -u git gitea gitea admin user change-password --username gitadmin --password НОВЫЙ_ПАРОЛЬ --must-change-password=false

# Создать администратора из командной строки
docker exec -u git gitea gitea admin user create --admin --username имя --password ПАРОЛЬ --email me@example.com --must-change-password=false

Пароль из командной строки остаётся в истории оболочки — после такой смены задайте постоянный в настройках профиля.

Частые ошибки

Сообщение Причина
ssh://git@localhost:2222/… в адресе не исправлены DOMAIN/SSH_DOMAIN в app.ini (шаг 3)
Permission denied (publickey) в профиле нет вашего ключа или добавлен не .pub; при нескольких ключах укажите нужный через ssh -i
port 2222: Connection refused контейнер остановлен — docker compose ps, docker start gitea
remote: Failed to authenticate user неверный логин/пароль; сброс — gitea admin user change-password
remote: Update your password пользователь ещё не входил в браузер и не сменил временный пароль
remote: Repository not found опечатка в адресе или коллега не добавлен в соавторы приватного репозитория
Author identity unknown не выполнены git config --global user.name и user.email

Официальная документация: https://docs.gitea.com.