Ручное развёртывание приложений замедляет циклы разработки, увеличивает риски ошибок и чревато простоями в работе сервисов. Современные проекты требуют автоматизированного подхода, где каждый код-коммит запускает чёткий процесс: сборку, тестирование и деплой без ручного вмешательства. Рассказываем, как реализовать полноценный CI/CD-пайплайн на базе Ubuntu 24.04 с тремя ключевыми технологиями — и запустить его на VPS для стабильной и управляемой среды развёртывания:
- GitHub Actions для автоматизированного управления процессами,
- Docker для создания переносимых контейнеризованных сред,
- NGINX как reverse-proxy с TLS-шифрованием.
Такая система:
- автоматически собирает и тестирует приложение при каждом обновлении кода,
- развёртывает обновления на продакшн-сервере без простоя,
- обеспечивает безопасное подключение через HTTPS,
- сохраняет возможность мгновенного отката.
Подготовка инфраструктуры
Начнём с настройки сервера на Ubuntu 24.04 LTS.
Установка базовых компонентов:
sudo apt update && sudo apt upgrade -y
sudo apt install docker.io docker-compose ufw -y
Настройка файрвола:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
Добавление пользователя для CI/CD:
sudo useradd -m deployer
sudo usermod -aG docker deployer
sudo mkdir -p /home/deployer/.ssh
Генерация SSH-ключа для GitHub Actions:
ssh-keygen -t ed25519 -f github-actions-key
sudo mv github-actions-key.pub /home/deployer/.ssh/authorized_keys
sudo chown -R deployer:deployer /home/deployer/.ssh
Настройка GitHub Actions
GitHub Actions — это система автоматизации, встроенная в GitHub. Она позволяет создавать пайплайны (цепочки действий), которые автоматически выполняются при определённых событиях, например, при отправке кода в репозиторий (push). Это ключевой инструмент для Continuous Integration/Continuous Deployment (CI/CD).
Создайте в корне репозитория файл .github/workflows/cicd.yml с содержимым:
name: CI/CD Pipeline # Название пайплайна
on: [push] # Пайплайн запускается при любом push в репозиторий
jobs:
build-test: # Задача № 1 — сборка и тестирование
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Cache Docker layers
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile') }}
- name: Build and test
run: |
docker build -t your-app:latest .
docker run your-app:latest npm test # Пример для Node.js
deploy: # Задача № 2 — деплой на сервер
needs: build-test
runs-on: self-hosted
steps:
- name: Deploy to production
env:
SSH_KEY: ${{ secrets.SERVER_SSH_KEY }}
run: |
echo "$SSH_KEY" > key.pem
chmod 600 key.pem
scp -i key.pem .env deployer@ip_сервера:/app/.env
ssh -i key.pem deployer@ip_сервера "cd /app && docker-compose pull && docker-compose up -d"
Во время выполнения первой задачи ваш репозиторий будет клонирован на виртуальную машину GitHub с последней версией Ubuntu, затем будет установлен Docker Buildx — расширенный инструмент для сборки образов, собран Docker-образ с тегом your-app:latest, включено кеширование слоёв Docker (ускоряет сборку на 40-70 % за счёт сохранения промежуточных образов), запущен контейнер и выполнены тесты (в примере — npm test для Node.js).
Вторая задача (деплой) выполняется на вашем собственном сервере с Ubuntu 24.04 и только в случае успешного выполнения первой задачи. Приложение будет развёрнуто на вашем сервере через SSH.
Настройка Self-Hosted Runner для GitHub Actions
GitHub Actions по умолчанию использует виртуальные машины GitHub (ubuntu-latest, windows-latest и другие). Но для деплоя на ваш сервер требуется специальный агент — Self-Hosted Runner. Это программа, которая:
- устанавливается непосредственно на ваш сервер Ubuntu,
- связывает сервер с вашим репозиторием GitHub,
- выполняет задачи CI/CD непосредственно на вашей инфраструктуре.
Для подключения сервера в настройках GitHub репозитория перейдите в Secrets → New repository secret и добавьте в SERVER_SSH_KEY приватный ключ из файла github-actions-key.
На сервере установите GitHub Actions Runner:
mkdir actions-runner && cd actions-runner
curl -o runner.tar.gz -L https://github.com/actions/runner/releases/download/v2.316.1/actions-runner-linux-x64-2.316.1.tar.gz
tar xzf ./runner.tar.gz
./config.sh --url https://github.com/ваш/репозиторий --token YOUR_TOKEN
./run.sh
Докеризация приложения
Создайте оптимизированный Dockerfile в корне проекта:
# Стадия сборки
FROM ubuntu:24.04 as builder
RUN apt update && apt install -y build-essential python3.12 git
COPY . /app
WORKDIR /app
RUN make build # Кастомная команда для вашего стека
# Финальный образ
FROM ubuntu:24.04
RUN apt update && apt install -y python3.12 && \
useradd -m appuser && chown -R appuser:appuser /app
USER appuser
COPY --from=builder --chown=appuser /app/dist /app
EXPOSE 8000
CMD ["python3", "/app/main.py"]
Критичные оптимизации:
- Мультистадийная сборка уменьшает итоговый образ на 60-80 % за счёт исключения инструментов компиляции.
- Безавторизационный пользователь appuser снижает риск эксплуатации уязвимостей.
- Временные файлы не попадают в образ.
Проверка безопасности:
docker scan your-app:latest
Деплой на Ubuntu-сервер
Создайте на сервере директорию /app с docker-compose.yml:
version: '3.8'
services:
app:
image: ghcr.io/your-org/your-app:latest
restart: always
env_file: .env
volumes:
- logs:/app/logs
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost:8000/health || exit 1"]
interval: 30s
timeout: 5s
retries: 3
nginx:
image: nginx:1.24-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./certs:/etc/letsencrypt
volumes:
logs:
Что мы сделали:
- При обновлении новый контейнер запускается параллельно со старым.
- NGINX перенаправляет трафик на новый контейнер после успешной проверки его состояния.
- Старая версия удаляется только после подтверждения работоспособности новой.
Настройка NGINX в качестве обратного прокси-сервера
Создайте конфигурацию /app/nginx.conf:
events {
worker_connections 1024;
}
http {
upstream app_backend {
server app:8000;
keepalive 32;
}
server {
listen 80;
server_name ваш_домен.зона;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name ваш_домен.зона;
ssl_certificate /etc/letsencrypt/live/ваш_домен.зона/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ваш_домен.зона/privkey.pem;
# Настройки безопасности TLS
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
location / {
proxy_pass http://app_backend;
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_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
}
Автоматизируйте SSL через Certbot. Сначала инициализируйте сертификат вручную:
docker run -it --rm --name certbot \
-v "/app/certs:/etc/letsencrypt" \
-v "/app/letsencrypt:/var/www/letsencrypt" \
certbot/certbot certonly \
--webroot -w /var/www/letsencrypt \
-d ваш_домен.зона
Добавьте cron для автообновления:
echo "0 3 * * * docker run --rm -v /app/certs:/etc/letsencrypt certbot/certbot renew" | sudo tee /etc/cron.d/certbot-renew
Итоговая схема работы
- Разработчик делает push кода в репозиторий.
- GitHub Actions запускает пайплайн:
- Клонирует код — получает актуальную версию репозитория.
- Собирает Docker-образ — создаёт контейнер с приложением, используя кеширование слоёв для ускорения.
- Запускает тесты — выполняет юнит- и интеграционные тесты внутри временного контейнера.
- Публикует образ — загружает собранный образ в GitHub Container Registry.
- При успешных тестах:
- GitHub Actions устанавливает SSH-соединение с вашим Ubuntu-сервером.
- Обновляет конфигурацию:
- Копирует файл окружения (.env),
- Останавливает старую версию приложения,
- Запускает новую версию через Docker Compose.
- Nginx как реверс-прокси обеспечивает плавное переключение:
- Перенаправляет запросы только на здоровые контейнеры.
- Старая версия остаётся активной, пока новая не пройдет проверку.
Такая настройка обеспечивает полный цикл CI/CD с гарантией качества и безопасности.
Не забывайте проверять работоспособность. После успешного деплоя выполните:
curl -I https://ваш_домен.зона
Ожидаемый ответ: HTTP/2 200 с заголовком X-Served-By: Docker-Container
Читайте в блоге:
- Ubuntu 24.04 LTS против 22.04: стоит ли обновляться
- Как установить и использовать Snap и Flatpak в Ubuntu 24.04
- Настройка Fail2ban на Ubuntu 24.04 LTS и защита от брутфорса