Перейти к основному содержанию

CI/CD на VPS: GitHub Actions + Docker Compose — полный гайд 2026

Настройка CI/CD на VPS с GitHub Actions и Docker Compose в 2026: SSH-деплой, self-hosted runner, zero-downtime, примеры для Node.js и Python.

CI/CD на VPS 2026: GitHub Actions + Docker Compose — полный гайд по автоматизации деплоя с zero-downtime и безопасностью

Зачем нужен CI/CD на VPS

CI/CD (Continuous Integration / Continuous Deployment) автоматизирует сборку, тестирование и развёртывание приложений. Вместо ручного деплоя по SSH каждый push в main-ветку запускает pipeline, который собирает Docker-образ, прогоняет тесты и обновляет приложение на VPS без простоя.

GitHub Actions встроен в сам GitHub, поэтому отдельный сервис заводить не нужно: по тарифам GitHub бесплатный лимит — 2 000 минут в месяц для приватных репозиториев, у публичных лимита нет. Плюс self-hosted runners на своём VPS и интеграция с Docker.

Что даёт CI/CD разработчику

  • Автоматический деплой — push в main обновляет production без ручных действий.
  • Раннее обнаружение ошибок — тесты прогоняются автоматически при каждом push, до попадания на сервер.
  • Воспроизводимость сборки — один и тот же Docker-образ запускается на dev и production, поэтому окружение приложения совпадает.
  • Откат — если образы помечены тегом по коммиту, предыдущую версию запускают одной командой; без тегов docker image prune из примеров ниже удаляет старый образ, и откат — это пересборка прошлого коммита.
  • Командная работа — каждый PR проходит проверки, merge в main автоматически деплоит.
Схема деплоя на VPS через GitHub Actions: push в main, workflow, SSH по ключу, docker compose up и проверка /health
Деплой через GitHub Actions: push в main, workflow, SSH по ключу из секретов, пересборка и проверка /health

Требования к VPS для CI/CD

Для комфортной работы CI/CD pipeline на VPS необходимы ресурсы для Docker-сборки и запуска self-hosted runner.

Минимальная конфигурация (1 проект)

  • CPU: 2 vCPU
  • RAM: 4 ГБ
  • Диск: 50 ГБ NVMe SSD
  • ОС: Ubuntu 22.04 или 24.04 LTS
  • Docker + Docker Compose
  • Статический IP-адрес

Для нескольких проектов

  • CPU: 4 vCPU
  • RAM: 8 ГБ
  • Диск: 100 ГБ NVMe SSD
  • Параллельные runners для разных репозиториев

Рекомендуемые провайдеры: Timeweb Cloud — Cloud MSK 50 за 1 080 ₽/мес при оплате за 12 месяцев со скидкой 10 %: 2 vCPU, 4 ГБ, 50 ГБ NVMe — ровно минимум выше. Списание почасовое. Младший MSK 40 дешевле, но там 2 ГБ памяти и 40 ГБ диска, то есть под этот минимум он не проходит. AdminVPS — Micro за 899 ₽/мес при оплате за месяц: 2 ядра 3,6 ГГц, 4 ГБ, 60 ГБ NVMe. Младшие Lite и Promo — 299 и 499 ₽/мес при оплате за месяц — под минимум выше не проходят: у них одно ядро и 1–2 ГБ памяти.

Реклама · ООО «Админвпс»

AdminVPS Micro — 899 ₽ при оплате за месяц

2 ядра 3,6 ГГц, 4 ГБ памяти, 60 ГБ NVMe, канал до 1 Гбит/с и 5 ТБ включённого трафика — это ровно минимальная конфигурация под один проект из списка выше, с запасом по диску: Docker-сборки и слои образов набираются быстро, и пятьдесят гигабайт кончаются раньше, чем ожидаешь. Self-hosted runner живёт на том же сервере. Под несколько проектов с параллельными runner'ами понадобится Start за 1 289 ₽ при оплате за месяц: 4 ядра и 8 ГБ. Резервные копии еженедельные и входят в тариф — для сервера, куда деплоит pipeline, это не мелочь. При оплате за 12 месяцев Micro стоит 9 170 ₽ за год вместо 10 788.

Год −15 %

Промокод EASYLINK — скидка 60 % на первый месяц при оплате за 1 месяц, кроме тарифов Lite.

Смотреть тариф Micro →

Подготовка VPS: установка Docker и Docker Compose

Все команды актуальны для Ubuntu 22.04/24.04 по состоянию на 2026 год. Скрипт get.docker.com — быстрый путь для тестового сервера; для боевого документация Docker советует ставить Docker из его apt-репозитория (раздел Install Docker Engine on Ubuntu на docs.docker.com).

Установка Docker

sudo apt update && sudo apt upgrade -y
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
docker --version

Установка Docker Compose

# Скрипт get.docker.com уже поставил плагин Compose — достаточно проверить:
docker compose version

# Если плагина нет (Docker ставили иначе), он ставится из репозитория Docker:
# sudo apt install docker-compose-plugin -y

Создание пользователя для деплоя

# Отдельный пользователь для безопасности
sudo useradd -m -s /bin/bash deployer
sudo usermod -aG docker deployer

# SSH-ключ для GitHub Actions
sudo su - deployer
ssh-keygen -t ed25519 -C "github-actions" -f ~/.ssh/github_deploy_key -N ""
cat ~/.ssh/github_deploy_key.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
cat ~/.ssh/github_deploy_key  # Скопировать в GitHub Secrets

Настройка GitHub Actions: SSH-деплой

Самый простой способ CI/CD — GitHub Actions деплоит на VPS через SSH. Не требует self-hosted runner.

Настройка секретов в GitHub

  1. Откройте репозиторий на GitHub
  2. Settings - Secrets and variables - Actions
  3. Добавьте секреты: VPS_HOST (IP сервера), VPS_USER (deployer), SSH_PRIVATE_KEY (приватный ключ), SSH_KNOWN_HOSTS (результат ssh-keyscan -H VPS_IP)

Workflow для Node.js приложения

# .github/workflows/deploy.yml
name: Deploy to VPS

on:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7

      - name: Setup SSH
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts

      - name: Deploy via SSH
        run: |
          ssh ${{ secrets.VPS_USER }}@${{ secrets.VPS_HOST }} << 'EOF'
            cd /app/myproject
            git pull origin main
            docker compose build
            docker compose up -d --remove-orphans
            docker image prune -f
          EOF

При каждом push в main: GitHub подключается к VPS по SSH, обновляет код через git pull, пересобирает Docker-образ и перезапускает контейнеры. Репозиторий на сервере клонируют заранее, приватный — с ключом развёртывания (Deploy key), который по умолчанию даёт только чтение.

Каталог и .env — до первого деплоя. У deployer нет sudo, и создать /app он не может: на проверке mkdir от его имени дал Permission denied. Каталог создаёт администратор, клонирует уже deployer, а файл .env с паролями кладут рядом с docker-compose.yml — в репозиторий он не попадает:

sudo mkdir -p /app && sudo chown deployer:deployer /app
sudo -iu deployer git clone АДРЕС_РЕПОЗИТОРИЯ /app/myproject
# затем /app/myproject/.env — что в нём, сказано у примера docker-compose.yml ниже

Self-hosted runner на VPS

Self-hosted runner — агент GitHub Actions, запущенный на вашем VPS. Билды выполняются локально, что быстрее и без лимитов на минуты.

Установка runner

# Создаём пользователя для runner
sudo useradd -m -s /bin/bash github-runner
sudo usermod -aG docker github-runner
sudo -iu github-runner

# Скачиваем runner (номер версии и ссылка — в Settings - Actions - Runners - New)
mkdir actions-runner && cd actions-runner
curl -o actions-runner-linux-x64-2.338.0.tar.gz -L \
  https://github.com/actions/runner/releases/download/v2.338.0/actions-runner-linux-x64-2.338.0.tar.gz
tar xzf actions-runner-linux-x64-2.338.0.tar.gz

# Настройка (токен из GitHub Settings - Actions - Runners - New)
./config.sh --url https://github.com/YOUR/REPO --token YOUR_TOKEN
exit

# Службу ставит администратор с sudo — от имени github-runner
cd /home/github-runner/actions-runner
sudo ./svc.sh install github-runner
sudo ./svc.sh start
sudo ./svc.sh status

Workflow с self-hosted runner

# .github/workflows/deploy.yml
name: Build & Deploy
on:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  build-and-deploy:
    runs-on: self-hosted  # Ваш runner на VPS
    steps:
      - uses: actions/checkout@v7

      - name: Build Docker image
        run: docker compose build

      - name: Run tests
        run: docker compose run --rm app npm test

      - name: Deploy
        run: |
          docker compose up -d --remove-orphans   # пересоздаст только изменившиеся сервисы
          docker image prune -f

      - name: Health check
        run: |
          sleep 10
          curl -f http://localhost:3000/health || exit 1

Self-hosted runner экономит время: нет задержки на запуск hosted runner и скачивание зависимостей. Docker-кеш хранится на VPS между запусками.

Docker Compose для production

Правильно настроенный docker-compose.yml — основа надёжного CI/CD. Включает healthcheck, restart policy, volumes и ограничения ресурсов.

Пример для Node.js

# docker-compose.yml
services:
  app:
    build: .
    ports:
      - "127.0.0.1:3000:3000"   # наружу — через Nginx: опубликованный порт идёт мимо ufw
    environment:
      - NODE_ENV=production
      - DATABASE_URL=${DATABASE_URL}
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]   # curl нужен в образе: в node:22-alpine и node:22-slim его нет
      interval: 30s
      timeout: 10s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "1.0"
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  pgdata:

Без двух вещей этот пример не поднимется. Первая — curl в образе: на проверке контейнер на node:22-alpine без него так и остался unhealthy (curl: executable file not found), хотя приложение отвечало. В Dockerfile на alpine хватает строки:

RUN apk add --no-cache curl

Вторая — файл .env рядом с docker-compose.yml. Без него переменные пустые: Postgres перезапускается по кругу с ошибкой «superuser password is not specified», а app не стартует вовсе — Compose ждёт здоровую базу.

DB_PASSWORD=придумайте-пароль
DATABASE_URL=postgres://postgres:придумайте-пароль@db:5432/postgres

Пример для Python (FastAPI/Django)

# docker-compose.yml
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    # pip install uvicorn-worker; для Django вместо main:app — myproject.asgi:application
    command: gunicorn main:app -w 4 -k uvicorn_worker.UvicornWorker -b 0.0.0.0:8000
    ports:
      - "127.0.0.1:8000:8000"
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - SECRET_KEY=${SECRET_KEY}
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    depends_on:
      redis:
        condition: service_healthy

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s

В python:3.12-slim curl тоже нет: без него проверка здоровья всегда unhealthy, хотя /health отвечает 200. В Dockerfile — до установки зависимостей:

RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
Docker Compose на VPS 2026: стек App + PostgreSQL + Redis + Nginx + SSL с автодеплоем через git push
Архитектура Docker Compose на VPS: App, PostgreSQL, Redis, Nginx с SSL — автодеплой из GitHub через git push

Zero-downtime деплой

Стандартный docker compose down && up вызывает простой: сервис недоступен, пока новый контейнер не запустится. Для production-сервисов это неприемлемо.

Пересборка одного сервиса

# Пересобрать и перезапустить только app, не трогая базу
docker compose up -d --no-deps --build app
# Compose сперва останавливает старый контейнер, потом запускает новый:
# простой короче, чем у down && up, но он есть

На нашей проверке 13.10.2026 (2 vCPU, 2 ГБ памяти) пересборка app этой командой заняла 11,6 секунды, а ответа не было около 0,5 секунды: при опросе каждые 0,1 секунды не дошли 5 запросов из 193. Маленькое приложение — у тяжёлого старт дольше, и окно шире.

Blue-Green деплой

# Два профиля: blue и green
# docker-compose.yml
services:
  app-blue:
    build: .
    ports: ["127.0.0.1:3001:3000"]
    profiles: ["blue"]
  app-green:
    build: .
    ports: ["127.0.0.1:3002:3000"]
    profiles: ["green"]

# Nginx переключает upstream:
# upstream app { server 127.0.0.1:3001; }  # blue
# upstream app { server 127.0.0.1:3002; }  # green

Blue-Green подход: деплоите новую версию параллельно, проверяете health, переключаете Nginx upstream, останавливаете старую версию. Нулевой простой.

Безопасность CI/CD pipeline

CI/CD pipeline имеет доступ к серверу и секретам. Безопасность критически важна.

  • Минимальные права — отдельный пользователь deployer без sudo. Но группа docker, по документации Docker, даёт права уровня root, поэтому ключ deployer берегут как ключ root.
  • SSH-ключи вместо паролей — ключ Ed25519, а ограничение по IP в authorized_keys — когда адрес раннера постоянный: у своего раннера или у больших раннеров GitHub со статическим IP. Адреса обычных раннеров GitHub меняются, и GitHub не советует вносить их в списки доступа.
  • Ротация секретов — обновляйте SSH-ключи и токены каждые 90 дней.
  • Ограничение self-hosted runner — не используйте на production-сервере для публичных репозиториев.
  • Сканирование образов — docker scout cves перед деплоем. Плагин Scout ставится отдельно: после скрипта get.docker.com команды docker scout на сервере нет.
  • GITHUB_TOKEN scope — минимальные permissions в workflow.
# Ограничение SSH по IP в authorized_keys (раннер с постоянным адресом)
from="203.0.113.0/24" ssh-ed25519 AAAA... github-actions

Проверка на сервере 13.10.2026: что работает по шагам

Наш замер 13.10.2026. На чистом сервере Timeweb Cloud (2 vCPU, 2 ГБ памяти) с Ubuntu 24.04 мы прошли разделы статьи по порядку: Docker из get.docker.com (29.9.0, Compose v5.6.0 уже внутри), пользователь deployer с ключом, SSH-деплой из workflow, оба примера docker-compose.yml, пересборку одного сервиса, профили blue-green и скачивание раннера.

  • Шаг Deploy via SSH из workflow сработал дословно: git pull, сборка и перезапуск заняли 13 секунд, на сервере новая версия. Но только после двух шагов, которых в статье не было, — каталога /app от администратора и файла .env; оба добавлены выше.
  • Пример для Node.js без .env не поднялся вовсе, с .env — поднялся, но проверка здоровья через curl в node:22-alpine давала unhealthy. С curl в образе — healthy через 32 секунды. В Python-примере то же с python:3.12-slim; команда gunicorn с uvicorn_worker.UvicornWorker верна — четыре воркера, около 114 МБ памяти.
  • Опубликованный порт идёт мимо ufw — подтвердилось: при ufw, где открыт только SSH, контейнер с -p 8081:80 отвечал снаружи, а с -p 127.0.0.1:8082:80 — нет.
  • Без SSH_KNOWN_HOSTS подключение обрывается на «Host key verification failed» — секрет нужен.

Чего проверка не показывает: сам GitHub — секреты, запуск workflow на его раннерах и регистрацию своего раннера (для неё нужен токен репозитория). Роль GitHub играл сам сервер: репозиторий-источник лежал на нём же, ключ и known_hosts — те же, что ушли бы в секреты. Приложения — наши пустые, а не ваш проект.

Заключение

Если сред нужно много — по одной на проект и на неделю-другую, — считать стоит не мощность, а время жизни: у почасового списания среда, прожившая неделю, обходится примерно в четверть месячного тарифа. Расчёт для агентства на пятнадцать проектов — в отдельном разборе.

CI/CD на VPS с GitHub Actions и Docker — это инвестиция в скорость и надёжность разработки. SSH-деплой подходит для простых проектов, self-hosted runner — для команд с частыми деплоями. Docker Compose обеспечивает воспроизводимость, а blue-green деплой — нулевой простой.

Для запуска CI/CD достаточно VPS с 2 vCPU и 4 ГБ RAM: 899 ₽/мес при оплате за месяц у AdminVPS (тариф Micro) или 1 080 ₽/мес при оплате за 12 месяцев у Timeweb Cloud (MSK 50). Сравните тарифы провайдеров с Docker-поддержкой на EasyLinkLife.

Реклама · АО «ТаймВэб»

VPS для CI/CD с Docker

Cloud MSK 50 — 2 vCPU, 4 ГБ RAM, 50 ГБ NVMe: 1 200 ₽/мес при оплате за месяц или 1 080 ₽/мес при оплате за 12 месяцев. Списание почасовое, Docker, GitLab и Portainer ставятся готовыми образами из маркетплейса.

Попробовать Timeweb Cloud

Реклама · ООО «Админвпс»

AdminVPS — сервер под self-hosted runner

Под сборку образов и прогон тестов берите Start: 4 vCPU, 8 ГБ RAM, 80 ГБ NVMe — 1 289 ₽/мес при помесячной оплате. Portainer для Docker-контейнеров есть в маркетплейсе провайдера.

Промокод EASYLINK — скидка 60 % на первый месяц при оплате за 1 месяц, кроме тарифов Lite.

Выбрать тариф AdminVPS

Реклама · ООО «Админвпс»

AdminVPS — сервер под CI/CD

Под раннеры и Docker Compose берите Start: 4 vCPU, 8 ГБ RAM, 80 ГБ NVMe — 1 289 ₽/мес при помесячной оплате. GitLab, Gitea и Portainer есть в каталоге приложений.

Промокод EASYLINK — скидка 60 % на первый месяц при оплате за 1 месяц, кроме тарифов Lite.

Выбрать тариф AdminVPS

Частые вопросы

Какой VPS нужен для CI/CD с GitHub Actions?

Минимум: 2 vCPU, 4 ГБ RAM, 50 ГБ NVMe SSD с Docker. Для нескольких проектов: 4 vCPU, 8 ГБ RAM. Такая конфигурация стоит 1 289 ₽/мес при оплате за месяц у AdminVPS (тариф Start) и 1 800 ₽/мес при оплате за 12 месяцев у Timeweb Cloud (MSK 80).

Self-hosted runner или SSH-деплой: что лучше?

SSH-деплой проще в настройке и подходит для 1-2 проектов. Self-hosted runner быстрее (локальный Docker-кеш), без лимитов на минуты и подходит для команд.

Безопасно ли использовать self-hosted runner?

Для приватных репозиториев — да. Для публичных — нет: любой PR может выполнить код на вашем сервере. Используйте отдельного пользователя без sudo.

Как настроить zero-downtime деплой на VPS?

Без простоя — blue-green деплой: новая версия запускается рядом со старой, Nginx переключается на неё после проверки health, старая останавливается. Команда docker compose up -d --no-deps --build пересобирает один сервис, не трогая базу, но короткий простой у неё остаётся: Compose останавливает старый контейнер до запуска нового.

Сколько стоит GitHub Actions?

На бесплатном тарифе GitHub — 2 000 минут в месяц на раннерах GitHub для приватных репозиториев; в публичных стандартные раннеры бесплатны. Свой раннер (self-hosted) GitHub не тарифицирует — платите только за VPS.

Можно ли использовать GitLab CI вместо GitHub Actions?

Да, GitLab CI работает аналогично с GitLab Runner. Настройка .gitlab-ci.yml вместо .github/workflows/. Принципы Docker-деплоя на VPS идентичны.

VPS для CI/CD с Docker

Выбрать VPS для деплоя

Некоторые ссылки в этой статье являются партнёрскими. Мы можем получить комиссию при переходе и покупке. На стоимость для вас это не влияет. Подробнее

Об авторе

Логотип EasyLinkLife

Редакция EasyLinkLife

Редакция EasyLinkLife сверяет цены, состав тарифов и условия оплаты российских хостинг-провайдеров по их первоисточникам.

Проверено: Редакция EasyLinkLife | Обновлено: 13 октября 2026 г.

Полезные материалы