CI/CD-пайплайн: автодеплой Spring Boot приложения на VPS через GitHub Actions
В этой статье разберем, как развернуть Spring Boot приложение на VPS и настроить автодеплой из GitHub. В качестве примера используем небольшой сервис - Weather Map Demo - и опубликуем его на поддомене weather.abykov.dev.
Арендуем сервер и настроим поддомен, systemd
-сервис, HTTPS через Nginx/Let’s Encrypt и CI/CD-пайплайн на GitHub Actions.
Что используем
- VPS на Ubuntu 24.04;
- Домен abykov.dev с поддоменом weather.abykov.dev;
- Java 21, Spring Boot 3, Maven;
- Nginx + Let’s Encrypt (HTTPS);
systemd
для запуска jar как сервиса;- GitHub Actions для сборки и деплоя.
Что сделаем пошагово
- Арендуем VPS и привяжем поддомен
weather.abykov.dev
к IP сервера (DNS A-запись); - Подготовим окружение на сервере: установим Java, создадим рабочие директории;
- Запустим приложение вручную и настроим его как
systemd
-сервис (переживает перезагрузку); - Включим Nginx как reverse-proxy и выпустим бесплатный SSL-сертификат Let’s Encrypt;
- Настроим GitHub Actions: Maven-сборка, копирование JAR на VPS по SSH и рестарт сервиса - автоматически при пуше в ветку
main
;
На выходе получим приложение, доступное по HTTPS на собственном домене, обновляемое одним коммитом в репозиторий.
Аренда VPS и домена
Для демонстрационного проекта - Spring Boot-приложения Weather Map Demo - арендуем VPS у хостинг-провайдера и приобретем домен abykov.dev
. Для обеспечения удобного доступа к приложению в панели управления DNS создадим A
-запись поддомена weather.abykov.dev
, указывающую на публичный IP-адрес VPS.
Заходим в DNS-панель, добавляем A
-запись для поддомена, указывая на IP VPS:
- Имя:
weather.abykov.dev
- Тип:
A
- Значение:
89.111.171.25
(IP адрес VPS) - TTL:
3600
Теперь при обращении к www.weather.abykov.dev
трафик будет направляться на наш VPS.
Настройка VPS
После приобретения VPS первым этапом является подготовка окружения - обновление системы и установка необходимых пакетов.
1
2
3
4
5
6
7
8
9
10
# обновляем систему
sudo apt update && sudo apt upgrade -y
# устанавливаем Java (например, OpenJDK 21)
sudo apt install openjdk-21-jdk -y
# создаем рабочую директорию под приложение
sudo mkdir -p /opt/weather-demo
sudo chown $USER:$USER /opt/weather-demo
В эту директорию (/opt/weather-demo/
) будет загружаться JAR-файл приложения.
Запуск Spring Boot вручную
После локальной сборки артефакт необходимо передать на сервер с помощью scp
:
1
2
3
scp \
api/target/api-0.0.1-SNAPSHOT.jar \
root@<IP_сервера>:/opt/weather-demo/weather-demo.jar
Затем выполнить запуск:
1
2
cd /opt/weather-demo
java -jar weather-demo.jar
⚠️ Для успешного запуска требуется fat jar (он же runnable jar или uber-jar). Такой JAR включает в себя все зависимости и формируется при использовании
spring-boot-maven-plugin
. Также это обязательное требование для GitHub Pages при работе с кастомными доменами.
В данном виде приложение работает, однако при перезапуске сервера процесс завершится. Чтобы обеспечить автоматический запуск и перезапуск при сбое, используется настройка systemd
-сервиса.
Автозапуск через systemd
Чтобы приложение продолжало работать после перезагрузки сервера и автоматически перезапускалось при сбоях, его следует оформить как сервис systemd
.
Создадим unit-файл /etc/systemd/system/weather-demo.service
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Unit]
Description=Weather Demo Spring Boot App
After=network.target
[Service]
User=root
WorkingDirectory=/opt/weather-demo
ExecStart=/usr/bin/java -jar /opt/weather-demo/weather-demo.jar
SuccessExitStatus=143
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Далее активируем и запускаем сервис:
1
2
3
sudo systemctl daemon-reload
sudo systemctl enable weather-demo
sudo systemctl start weather-demo
Проверить состояние можно командой:
1
systemctl status weather-demo
Для просмотра логов приложения используется journalctl
:
1
journalctl -u weather-demo -f
Теперь приложение работает как полноценный сервис: оно стартует вместе с системой, перезапускается при сбое и не зависит от активной сессии в терминале.
Настройка доступа по домену и HTTPS
По умолчанию приложение на Spring Boot запускается на порту 8080
и доступно только по IP-адресу сервера. Чтобы открыть его по доменному имени без указания порта, применяется один из двух подходов, рассматриваемых ниже.
Безотносительно подходов, следует выполнить настройку server.address
.
Настройка server.address
Важный момент: по умолчанию Spring Boot может слушать только localhost
(127.0.0.1
), из-за чего приложение доступно лишь внутри самого сервера.
При запуске Spring Boot поднимает встроенный веб-сервер (обычно Tomcat). Параметр server.address
определяет, на каком IP-адресе он будет слушать входящие соединения.
Варианты значений:
127.0.0.1
(илиlocalhost
). Приложение доступно только внутри сервера (curl http://127.0.0.1:8080
). Извне (по IP или домену) открыть не получится.0.0.0.0
. Специальное значение: слушать на всех интерфейсах. Тогда сервис доступен изнутри сервера (localhost:8080
), по публичному IP (89.111.171.25:8080
), через домен (weather.abykov.dev
).- Конкретный IP (например
192.168.1.100
). Сервер слушает только на этом интерфейсе, иногда полезно для ограниченного доступа.
Так как по умолчанию сервер слушает localhost
, запросы снаружи блокируются. После установки server.address=0.0.0.0
приложение становится доступным и по IP, и по домену.
Вариант 1. Spring Boot на 80 порту
В application.properties
можно задать:
1
2
server.address=0.0.0.0
server.port=80
После перезапуска сервис станет доступен по адресу: http://weather.abykov.dev
Однако у такого решения есть недостаток: при размещении нескольких приложений придется вручную распределять порты.
Вариант 2. Nginx как reverse-proxy (рекомендуется)
Более универсальный подход - использовать Nginx в качестве обратного прокси: Spring Boot продолжает работать на 8080
, а Nginx принимает запросы на 80
и перенаправляет их внутрь.
Что такое обратное проксирование?
Стоит пояснить термин. Существует два вида прокси-серверов:
- Forward proxy (прямой прокси) - работает на стороне клиента (стоит перед пользователем). Пользователь отправляет запрос не напрямую к сайту, а через прокси. Пример: корпоративный прокси-сервер, который фильтрует трафик сотрудников или скрывает их IP.
- Reverse proxy (обратный прокси) - работает на стороне сервера (стоит перед сервером). Пользователь обращается к единому адресу (например,
weather.abykov.dev
), а прокси перенаправляет запрос во внутренние сервисы (например, на Spring Boot приложение на порту8080
).
Именно поэтому его называют “обратным” - он скрывает внутренние сервисы за единым фасадом.
Визуально схема выглядит так:
1
2
Клиент -> Forward Proxy -> Интернет (сайты)
Клиент -> Reverse Proxy -> Внутренние сервисы (API, приложения)
В нашем случае Nginx выполняет роль reverse proxy
: он принимает HTTP-запросы на 80
-м порту и проксирует их к приложению Spring Boot на порту 8080
.
Установка и настройка Nginx:
1
sudo apt install nginx -y
Создание конфигурации /etc/nginx/sites-available/weather.abykov.dev
:
1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
server_name weather.abykov.dev;
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
}
Активация сайта и проверка конфигурации::
1
2
3
sudo ln -s /etc/nginx/sites-available/weather.abykov.dev /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
Теперь приложение открывается по адресу http://weather.abykov.dev.
HTTPS через Let’s Encrypt
Для защиты трафика необходимо настроить HTTPS. Самый удобный способ - использовать Let’s Encrypt, который позволяет бесплатно выпустить TLS-сертификат.
Устанавливаем certbot
и плагин для Nginx:
1
sudo apt install certbot python3-certbot-nginx -y
Запрашиваем сертификат для поддомена:
1
sudo certbot --nginx -d weather.abykov.dev
После выполнения команда certbot автоматически:
- Обновит конфигурацию Nginx;
- Добавит директиву
listen 443 ssl
; - Пропишет пути к сертификату и ключу;
- Настроит редирект с HTTP → HTTPS.
Теперь сайт доступен по защищенному протоколу: https://weather.abykov.dev
.
Сертификат Let’s Encrypt действует 90 дней, но certbot настраивает автоматическое продление. Проверить можно так:
1
sudo systemctl status certbot.timer
⚠️ Даже если у основного домена (
abykov.dev
) уже был установлен SSL-сертификат (например, AlphaSSL), он не распространяется автоматически на поддомены. Дляweather.abykov.dev
требуется отдельный сертификат или wildcard-сертификат (*.abykov.dev
). В нашем случае был использован Let’s Encrypt, так как это бесплатное и удобное решение.
Автоматический деплой через GitHub Actions
Развернутое вручную приложение решает задачу, но требует постоянного копирования JAR-файла и перезапуска сервиса вручную при каждом обновлении кода. Это неудобно и не подходит для продуктивной разработки.
Лучшее решение - настроить CI/CD-пайплайн на GitHub Actions, который будет:
- Собирать проект при каждом пуше в ветку main;
- Передавать собранный JAR на VPS по SSH;
- Перезапускать systemd-сервис.
Конфигурация GitHub Actions
Для этого в проекте создаем файл .github/workflows/deploy.yml
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
name: Deploy to VPS
on:
push:
branches:
- main # Автодеплой будет запускаться только при пуше в main
jobs:
deploy:
runs-on: ubuntu-latest # GitHub Actions будет использовать виртуалку с Ubuntu
steps:
# 1. Клонируем репозиторий
- name: Checkout code
uses: actions/checkout@v4
# 2. Устанавливаем JDK 21 (нужен для сборки Spring Boot проекта)
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
# 3. Собираем проект через Maven (без тестов для ускорения)
- name: Build with Maven
run: mvn -B clean package -DskipTests
# 4. Проверяем, что index.html попал в JAR (отладочный шаг)
- name: Verify index.html inside JAR
run: |
echo "Check if index.html is inside JAR..."
jar tf api/target/api-0.0.1-SNAPSHOT.jar \
| grep index.html || echo "index.html not found!"
echo "----- index.html content -----"
unzip -p api/target/api-0.0.1-SNAPSHOT.jar \
BOOT-INF/classes/templates/index.html \
|| echo "No index.html inside JAR"
echo "------------------------------"
# 5. Копируем JAR на VPS по SSH
- name: Copy JAR to VPS
uses: appleboy/scp-action@v0.1.7
with:
host: $ # IP адрес VPS
username: $ # Пользователь
key: $ # SSH ключ из GitHub Secrets
source: "api/target/api-0.0.1-SNAPSHOT.jar"
target: "/opt/weather-demo/"
overwrite: true
# 6. Перемещаем JAR в правильное место на сервере
- name: Move JAR into place
uses: appleboy/ssh-action@v1.0.0
with:
host: $
username: $
key: $
script: |
mv /opt/weather-demo/api/target/api-0.0.1-SNAPSHOT.jar \
/opt/weather-demo/weather-demo.jar
# 7. Перезапускаем systemd-сервис
- name: Restart service
uses: appleboy/ssh-action@v1.0.0
with:
host: $
username: $
key: $
script: |
sudo systemctl stop weather-demo || true
sudo systemctl start weather-demo
sudo systemctl status weather-demo --no-pager
Подготовка SSH-ключей для GitHub Actions
Для безопасного копирования артефактов на сервер через GitHub Actions используется аутентификация по SSH-ключам. Нам понадобится сгенерировать пару ключей: приватный (для GitHub) и публичный (для VPS).
Генерация ключей
На локальной машине выполним:
1
ssh-keygen -t ed25519 -C "github-actions" -f github_actions
Здесь:
-t ed25519
- современный и безопасный алгоритм;-C "github-actions"
- комментарий, чтобы было понятно, для чего ключ;-f github_actions
- имя файлов (получатсяgithub_actions
иgithub_actions.pub
).
В результате будут созданы два файла:
github_actions
- приватный ключ (его кладем в GitHub Secrets);github_actions.pub
- публичный ключ (его добавляем на VPS).
Установка ключа на VPS
Подключаемся к серверу:
1
ssh root@<IP_сервера>
И добавляем публичный ключ в файл ~/.ssh/authorized_keys
:
1
2
cat github_actions.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Теперь GitHub Actions сможет подключаться по SSH без пароля.
Secrets в GitHub
Чтобы pipeline имел доступ к VPS, необходимо добавить следующие секреты в настройках репозитория GitHub (Settings → Secrets and variables → Actions):
VPS_HOST
- IP-адрес сервера;VPS_USER
- пользователь для подключения;VPS_SSH_KEY
- приватный SSH-ключ для доступа.
После этого пайплайн сможет без проблем подключаться к серверу и деплоить обновления.
Результат
После всех настроек, при каждом коммите в ветку main
GitHub Actions автоматически соберет JAR, загрузит его на VPS в /opt/weather-demo/
, перезапустит сервис weather-demo
.
Таким образом, весь деплой сводится к одному действию - git push
.
Дополнительно: уведомления о деплое
После настройки CI/CD полезно получать уведомления о том, успешно ли прошел деплой. GitHub Actions поддерживает интеграции с популярными мессенджерами (Slack, Telegram, Matrix и др).
Пример: уведомления в Matrix
1
2
3
4
5
6
7
8
9
- name: Notify Matrix room
uses: matrix-org/matrix-message-action@v1
with:
homeserver: "https://matrix.org"
access_token: $
room_id: "!yourRoomId:matrix.org"
message: |
✅ Deploy completed successfully on weather.abykov.dev
Здесь:
MATRIX_ACCESS_TOKEN
хранится в GitHub Secrets и генерируется в Matrix (для бота или пользователя).room_id
- уникальный идентификатор комнаты, его можно найти в настройках Matrix.
Другие варианты
- Slack: через slackapi/slack-github-action;
- Telegram: через appleboy/telegram-action;
- Email: через стандартный
actions/send-mail
Таким образом, можно получать уведомления о каждом деплое прямо в рабочий чат.