Деплой приложения на Selectel VPS
Пошаговый guide: от создания виртуальной машины на Selectel до CI/CD, мониторинга и резервного копирования. Примерное время выполнения — 2–4 часа в зависимости от автоматизации.
Статья была полезной?
Пошаговый guide: от создания виртуальной машины на Selectel до CI/CD, мониторинга и резервного копирования. Примерное время выполнения — 2–4 часа в зависимости от автоматизации.
Статья была полезной?
Selectel предоставляет гибкую инфраструктуру VPS с поддержкой private network, Floating IP и S3-совместимого Object Storage, актуально в 2026 году. Для деплоя важны: предсказуемая сеть 1 Gbit/s, доступ к snapshots и API для автоматизации, а также возможность подключить DDoS-поддержку на уровне сети.
Для задач CI/CD и мониторинга Selectel подходит благодаря простому управлению образами и возможностям выделенного диска NVMe. Если вы планируете масштабировать приложение — можно использовать private network для кластеризации и выделенные блочные диски для баз данных.
Задача: создать VPS с Ubuntu 24.04 и автоматически установить Docker 24 и базовый набор пакетов через cloud-init.
Команда/действие (через Control Panel или API): вставьте cloud-init в поле «User data» при создании VPS. Пример cloud-init, который создаст пользователя deployer, установит Docker 24 (официальный репозиторий) и nginx 1.26 и включит firewall:
#cloud-config
users:
- name: deployer
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
ssh-authorized-keys:
- ssh-rsa AAAA...your_public_key...
packages:
- apt-transport-https
- ca-certificates
- curl
- gnupg
runcmd:
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmour -o /usr/share/keyrings/docker-archive-keyring.gpg
- echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list
- apt-get update -y && apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin nginx=1.26.*
- systemctl enable --now docker
- ufw allow OpenSSH
- ufw allow 80/tcp
- ufw allow 443/tcp
- echo "y" | ufw enable
- systemctl enable --now nginx
Пояснение: cloud-init выполняется при первом запуске VM; установка Docker из официального репозитория гарантирует версию Docker 24.x (релиз 2025). nginx 1.26 устанавливается через apt-репозиторий Ubuntu, точная версия 1.26.* будет выбранa если есть доступный пакет.
Ожидаемый вывод (успех):
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2026-02-12 10:15:23 UTC; 2min ago
Типичная ошибка и как её исправить:
Ошибка:
Job for docker.service failed because the control process exited with error code.
See "systemctl status docker.service" and "journalctl -xe" for details.
Фикс:
- Проверьте, что пакет containerd.io установлен и совместим с версией docker-ce.
- Запустите вручную: apt-get install -f && systemctl restart docker
- Если ошибка "no space left on device", проверьте размер корневого диска и увеличьте его или подключите блочный диск.
Задача: привязать домен, выпустить TLS сертификат и настроить firewall + маршрутизацию для внутренних сервисов.
Действия и команды:
# Добавьте запись A в DNS: example.com → <ваш_floating_ip>
# На сервере установите certbot 2.x и получите сертификат (пример для nginx):
sudo apt-get update -y && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com --non-interactive --agree-tos -m admin@example.com
Пояснение: certbot 2.x (релиз 2024–2025) автоматизирует конфигурацию nginx. Если nginx слушает нестандартный порт, используйте certbot certonly и настройте конфигурацию вручную.
Ожидаемый вывод (успех):
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/example.com/fullchain.pem
- Your cert will expire on 2026-05-01. To obtain a new version of the certificate in the
future, simply run certbot again.
Типичная ошибка и как её исправить:
Ошибка:
Failed authorization procedure. example.com (http-01): urn:ietf:params:acme:error:connection
Фикс:
- Проверьте, что DNS A запись указывает на публичный IP и DNS распространяется (propagation) — 1–10 минут.
- Убедитесь, что ufw разрешает 80/tcp и 443/tcp.
- Если используется Cloudflare или другой прокси, временно отключите проксирование/включите "DNS only".
Настройка UFW (пример, быстрые команды):
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
Ожидаемый вывод для ufw status verbose:
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp ALLOW IN Anywhere
80/tcp ALLOW IN Anywhere
443/tcp ALLOW IN Anywhere
Задача: настроить GitHub Actions для сборки образа и публикации в GitHub Container Registry (ghcr.io). Этот этап делает сборку воспроизводимой и позволяет запускать тесты на каждом коммите.
Пример workflow (файл .github/workflows/ci.yml):
name: CI
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to ghcr
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/${{ github.repository_owner }}/myapp:latest
Пояснение: этот workflow собирает образ, использует Buildx и пушит в ghcr.io. Время выполнения: для образа ~120 MB — 30–90 секунд на GitHub Actions; для больших сборок — несколько минут.
Ожидаемый вывод (успех): фрагмент логов:
Step 1/10 : FROM node:20-alpine
---> 1a2b3c4d5e6f
Successfully built 123abc456def
Successfully tagged ghcr.io/owner/myapp:latest
Pushed ghcr.io/owner/myapp:latest
Типичная ошибка и как её исправить:
Ошибка:
Error: unauthorized: authentication required
Фикс:
- Убедитесь, что workflow использует правильные credentials: для ghcr используйте ${ secrets.GITHUB_TOKEN } или Personal Access Token с scope write:packages.
- Проверьте, что в репозитории включена опция Packages и разрешено чтение/публикация пакетов.
Задача: при успешном CI автоматически доставлять новый образ на VPS и перезапускать сервис без потерь. Подход — GitHub Action, который подключается по SSH и выполняет обновление образа и рестарт systemd unit.
Пример деплой-шага в GitHub Actions (snippet):
- name: Deploy to Selectel VPS
uses: appleboy/ssh-action@v0.1.8
with:
host: ${{ secrets.VPS_HOST }}
username: deployer
key: ${{ secrets.VPS_SSH_KEY }}
script: |
sudo docker pull ghcr.io/${{ github.repository_owner }}/myapp:latest
sudo docker tag ghcr.io/${{ github.repository_owner }}/myapp:latest myapp:current
sudo systemctl restart myapp.service
Systemd unit для контейнера (/etc/systemd/system/myapp.service):
[Unit]
Description=MyApp container
After=network.target docker.service
Requires=docker.service
[Service]
Restart=always
ExecStartPre=-/usr/bin/docker rm -f myapp
ExecStart=/usr/bin/docker run --name myapp -p 127.0.0.1:8080:8080 --env-file /etc/myapp/env -v /var/myapp/data:/data ghcr.io/OWNER/myapp:latest
ExecStop=/usr/bin/docker stop myapp
[Install]
WantedBy=multi-user.target
Пояснение: systemd контролирует контейнер и позволяет делать откат через восстановление старого тега. Привязка к 127.0.0.1 предназначена для использования с nginx как reverse-proxy на порту 80/443.
Ожидаемый вывод после рестарта:
● myapp.service - MyApp container
Loaded: loaded (/etc/systemd/system/myapp.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2026-03-05 08:45:12 UTC; 3s ago
Типичная ошибка и как её исправить:
Ошибка:
Failed to start myapp.service: Unit myapp.service failed to load: No such file or directory.
Фикс:
- Проверьте путь файла и права: sudo systemctl daemon-reload
- Убедитесь, что image доступен (docker pull ghcr.io/...)
- Если ошибка при запуске контейнера "port is already allocated", проверьте, что другой процесс не держит порт 8080 и используйте netstat или ss.
Задача: развернуть базовый стек мониторинга: node_exporter, Prometheus 2.45 и Grafana 10. Удобно запускать каждый компонент в контейнере и хранить данные Prometheus на отдельном блочном диске.
Пример docker-compose.yml (упрощённо):
version: '3.8'
services:
node_exporter:
image: prom/node-exporter:1.6.1
restart: unless-stopped
network_mode: host
pid: host
prometheus:
image: prom/prometheus:v2.45.0
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
ports:
- "9090:9090"
grafana:
image: grafana/grafana:10.0.0
ports:
- "3000:3000"
volumes:
prometheus_data:
Пример минимальной конфигурации prometheus.yml:
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'node'
static_configs:
- targets: ['localhost:9100']
- job_name: 'myapp'
static_configs:
- targets: ['127.0.0.1:8080']
Пояснение: node_exporter собирает метрики сервера (CPU, memory, disk). Prometheus опрашивает его каждые 15 секунд. Grafana подключается к Prometheus как datsource и предоставляет dashboards.
Ожидаемый вывод после запуска docker-compose:
Creating network "default" with the default driver
Creating volume "prometheus_data" with default driver
Creating prometheus_1 ... done
Creating grafana_1 ... done
Типичная ошибка и как её исправить:
Ошибка:
prometheus_1 | error opening storage: mkdir /prometheus: permission denied
Фикс:
- Убедитесь, что каталог для тома доступен и владелец установлен: sudo chown -R 65534:65534 ./prometheus_data
- Для долгоживущей установки используйте отдельный блочный диск и смонтируйте его в /var/lib/prometheus.
Задача: настроить ежедневный бэкап PostgreSQL в Selectel Object Storage (S3-совместимый) и базовые меры безопасности: обновления, fail2ban, ротация логов.
Пример скрипта для бэкапа (файл /usr/local/bin/pg_backup.sh):
#!/bin/bash
set -e
TIMESTAMP=$(date -u +"%Y%m%dT%H%M%SZ")
BACKUP_FILE=/tmp/pg_backup_${TIMESTAMP}.sql.gz
PGPASSWORD="${PGPASSWORD}" pg_dump -h localhost -U myapp userdb | gzip > ${BACKUP_FILE}
# Загрузка в S3 (rclone настроен как selectel)
rclone copy ${BACKUP_FILE} selectel:backups/myapp/ --s3-acl private
rm -f ${BACKUP_FILE}
Добавьте в cron (ежедневно в 03:00 UTC):
0 3 * * * /usr/local/bin/pg_backup.sh >> /var/log/pg_backup.log 2>&1
Ожидаемый вывод при успешном запуске скрипта (строки из лога):
2026-03-10T03:01:23Z Uploaded: backups/myapp/pg_backup_20260310T030123Z.sql.gz
Типичная ошибка и как её исправить:
Ошибка:
pg_dump: error: connection to database "userdb" failed: fe_sendauth: no password supplied
Фикс:
- Убедитесь, что переменная окружения PGPASSWORD задана в /etc/myapp/env или используйте .pgpass для автоматической аутентификации.
- Проверьте доступность порта 5432 и права пользователя PostgreSQL.
Рекомендации по безопасности:
sudo apt-get update && sudo unattended-upgrades (рекомендуется включить unattended-upgrades для базовых патчей).Выбор тарифа зависит от нагрузки. Минимальная конфигурация для тестового проекта: 1 vCPU и 2 GB RAM (для статических сайтов, API с низкой нагрузкой). Для типичного веб-приложения с PostgreSQL и Redis рекомендую 2 vCPU и 4 GB RAM, SSD NVMe 40–80 GB. Для баз данных и высоких нагрузок — 4 vCPU и 8–16 GB RAM с отдельным блочным диском под данные.
Технические рекомендации по дискам и сетям (2026):
Selectel предоставляет техподдержку через Control Panel, API и тикеты; у клиентов с коммерческими SLA есть приоритет 24/7. Для оперативного решения инцидентов используйте: создание тикета в Control Panel, звонок в поддержку (если доступен по тарифу) и отправку логов в тикет.
Практические советы при обращении в поддержку:
journalctl -u myapp.service --since "1 hour ago", docker logs myapp, и снимки состояния ss -tulpen.
Скриншот панели Selectel с созданным VPS и cloud-init

Скриншот Grafana с дашбордом Prometheus, мониторинг CPU и сети
Для привязки Floating IP используйте Control Panel Selectel: в разделе «Сеть» выберите Floating IP и привяжите к нужному VPS. Альтернативно можно применить API: создайте Floating IP и выполните action attach к ресурсному ID VPS. После привязки убедитесь, что в системе настроены правила firewall и что nginx слушает адрес 0.0.0.0 или 127.0.0.1 в зависимости от топологии. Изменение DNS записи (A) на Floating IP может требовать 1–15 минут для распространения.
Отследите потребление через docker stats и Prometheus. Для быстрого решения можно задать лимиты в docker run (флаги --memory и --cpus) или в docker-compose с секцией deploy.resources. Если процесс памяти растёт из-за утечки — включите профилирование приложения (heap snapshots), перезапускайте контейнеры с ограничением и планируйте rolling-restart. Для базы данных увеличьте RAM или выносите кеш в Redis на отдельный VPS/managed-сервис.
Чаще всего причина в правах: используемый токен не имеет scope для публикации. Для ghcr используйте ${{ secrets.GITHUB_TOKEN }} (автоматически доступен) или Personal Access Token с permission write:packages. Проверьте также, не включён ли в репозитории requirement for review перед push, и что organisation не блокирует внешние registrations. Логи шага docker/login-action обычно содержат подсказку — unauthorized или insufficient_scope.
Секреты CI (ключи доступа к registry, SSH-ключи) храните в GitHub Secrets и передавайте в job. На VPS используйте менеджер секретов (Vault) или храните переменные в файле /etc/myapp/env с правами 600 и владельцем deployer. Не храните секреты в репозитории. Для автоматизированного восстановления после катастроф используйте шифрованные бэкапы (gpg) перед отправкой в Object Storage.
Комментарии (0)
Войдите или зарегистрируйтесь, чтобы оставить комментарий
Загрузка комментариев…