Содержание

Фраза «у меня работает» редко описывает реальную работоспособность продукта. Обычно она означает, что приложение запускается в одной среде и ломается в другой, а процесс выкладки на сервер не воспроизводим. В результате часть времени уходит не на исправление дефекта, а на выяснение того, какая именно версия кода и зависимостей оказалась на продакшене, какие переменные окружения были выставлены и какие команды запускались «вручную».

Типовой источник таких историй – ручное копирование файлов на виртуальный сервер (VPS/VDS), запуск сборки прямо на сервере, правки конфигурации «по месту», отсутствие единого артефакта релиза и журнала изменений. Ниже разобран практический сценарий, который закрывает именно эту проблему: сборка в CI и автоматический деплой на собственный сервер без ручных переносов, с возможностью проверки и отката.

Когда «у меня работает» становится инцидентом

Ручной деплой на VPS обычно выглядит так: архив или директория с проектом копируется по SFTP/rsync, затем на сервере выполняются команды «на память» (установка зависимостей, миграции, перезапуск сервисов). На каждом шаге возможны расхождения, которые не видны в момент выкладки:

  • Дрейф окружения – разные версии Node.js/Python/PHP/Java, системных библиотек, утилит сборки, расширений
  • Непредсказуемые зависимости – отсутствие lock-файлов или их игнорирование приводит к установке «свежих» версий пакетов
  • Смешивание релизов – частичное копирование директории оставляет старые файлы рядом с новыми
  • Секреты и конфиги «внутри проекта» – случайная передача .env в репозиторий или копирование «чужих» конфигураций на сервер
  • Отсутствие трассируемости – непонятно, какой коммит и какой набор параметров сейчас в продакшене
  • Человеческий фактор – забытый флаг, пропущенный шаг, перезапуск не того сервиса

В подобных сценариях неделя легко уходит на реконструкцию состояния сервера и воспроизведение «той самой» сборки. Единственный устойчивый способ убрать спор – сделать так, чтобы сервер получал строго определенный артефакт релиза, а все операции деплоя выполнялись одинаково при каждом запуске.

Что именно должно дать автоматизация

Сборка и деплой «без ручных копирований» – не про скорость нажатия кнопок. Это про свойства процесса, которые защищают от расхождений сред:

  • Единый артефакт – бинарник, архив или контейнерный образ, построенный один раз и разворачиваемый в неизменном виде
  • Повторяемость – сборка одинаково проходит на любом агенте CI, а деплой выполняется одним и тем же скриптом
  • Версионирование – артефакт привязан к коммиту (SHA) или тегу релиза
  • Прозрачность – логи сборки и деплоя сохраняются в CI, шаги формализованы
  • Откат – возврат на предыдущую версию занимает минуты, а не часы
  • Минимальные привилегии – CI имеет ровно тот доступ, который нужен для выкладки, без лишних прав

Эти свойства реализуются разными способами. Дальше – краткий разбор вариантов и затем референсная схема для VPS/VDS, которая чаще всего дает предсказуемый результат.

Три рабочих модели деплоя на VPS/VDS

Вариант 1. «Git pull» на сервере

Суть: сервер подключается к репозиторию и забирает код командой git pull, после чего запускается сборка/перезапуск.

Плюсы: минимальная инфраструктура, быстро стартовать.

Минусы: сборка и зависимости зависят от состояния сервера; сложно гарантировать, что на сервере стоит нужная версия рантайма; выше риск «поправить руками» и забыть; откат требует аккуратного управления ветками/тегами и повторных сборок.

Когда уместно: прототипы и проекты без строгих требований к воспроизводимости.

Вариант 2. Сборка артефакта в CI и загрузка на сервер

Суть: CI собирает архив (например, фронтенд build или бинарник), затем загружает на сервер по SSH (scp/rsync), переключает симлинк на новый релиз и перезапускает процесс через systemd.

Плюсы: артефакт фиксирован; сервер не выполняет тяжелую сборку; удобно для статических сайтов, Go/Java/.NET.

Минусы: для приложений с большим числом зависимостей сложнее обеспечить совпадение системных библиотек; часто появляется «зоопарк» скриптов под разные типы проектов.

Когда уместно: сервисы без контейнеризации или когда контейнеры запрещены политикой.

Вариант 3. Контейнерный образ собирается в CI, сервер только запускает

Суть: CI собирает Docker-образ, публикует его в registry, затем сервер делает docker pull и перезапускает сервис через Docker Compose (или оркестратор).

Плюсы: высокая воспроизводимость; одинаковый рантайм на dev/CI/prod; простая трассируемость по тегу образа; удобный откат на предыдущий тег.

Минусы: нужен Docker на VPS; требуется дисциплина по секретам и хранению образов; для тяжелых образов важно продумать размер и кэширование.

Когда уместно: большинство веб‑приложений и API на VPS/VDS, где требуется стабильный релизный процесс.

Дальше рассматривается именно третий вариант – как самый надежный для устранения «у меня работает» в реальных внедрениях на виртуальных серверах.

Референсный сценарий: CI собирает образ, VPS деплоится через SSH и Docker Compose

Идея – разделить ответственность: CI отвечает за сборку и тесты, сервер отвечает за запуск и маршрутизацию трафика. В результате на сервер перестают попадать «полусобранные» состояния, а ручное копирование файлов заменяется на формализованный деплой одной командой из CI.

Базовая схема:

  • Коммит попадает в основную ветку репозитория
  • CI выполняет тесты, собирает контейнерный образ и пушит его в registry с тегом коммита
  • CI по SSH подключается к VPS/VDS и запускает короткий деплой‑скрипт: docker login → docker pull → docker compose up -d
  • После перезапуска выполняется проверка health endpoint; при проблеме выполняется откат на предыдущий тег

В качестве площадки подходит любой провайдер, где доступна аренда VPS/VDS с публичным IP, SSH‑доступом и возможностью установить Docker. В некоторых проектах требуется минимальная задержка до российских пользователей и интеграций – тогда выбираются площадки с размещением в Москве, например VPS.house (как один из множетсва примеров сервиса аренды виртуальных серверов).

ЧИТАТЬ ТАКЖЕ:  Зачем нужна Low-code платформа в бизнесе

Шаг 1. Подготовка виртуального сервера

Пример ниже ориентирован на Ubuntu 22.04 LTS или Debian 12. На других дистрибутивах меняются названия пакетов и команды управления сервисами, но логика остается прежней.

Минимальные действия перед подключением CI:

  • обновление системы и включение автоматических security‑обновлений (по политике проекта)
  • создание отдельного пользователя для деплоя (не root)
  • настройка SSH только по ключам; отключение password‑логина при возможности
  • базовый firewall (например, UFW) с открытыми портами 22/80/443
  • установка Docker и Compose plugin
  • создание каталога проекта, где будет лежать docker-compose.yml и env‑файлы

Пример команд (адаптировать под конкретную ОС):
sudo apt update && sudo apt upgrade -y
sudo adduser deploy
sudo usermod -aG sudo deploy
sudo mkdir -p /opt/app
sudo chown deploy:deploy /opt/app
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Пользователь deploy получает права на обслуживание приложения, но не используется для интерактивной работы «на продакшене». Это снижает вероятность случайных правок и упрощает аудит.

Шаг 2. Установка Docker и настройка прав

Для продакшен‑сценариев предпочтительнее установка Docker из официального репозитория, чтобы получать предсказуемые версии и обновления. После установки пользователь deploy добавляется в группу docker (или деплой выполняется через sudo, если политика безопасности требует запретить доступ к docker‑сокету).

Критический момент безопасности: доступ к docker‑сокету по факту равен root‑доступу. Если проект требует строгого разграничения, деплой выполняется через sudo с ограниченным набором команд или через отдельный механизм (например, pull‑модель с agent/runner на сервере).

Шаг 3. Reverse proxy и TLS

На VPS/VDS чаще всего используется Nginx в роли reverse proxy: он принимает HTTPS, отдает статические файлы (при наличии) и проксирует запросы в контейнер приложения. TLS‑сертификаты обычно выдаются через ACME (например, certbot). Для деплоя это важно по двум причинам:

  • проверка работоспособности после релиза должна выполняться по тому же домену и протоколу, что и пользовательский трафик
  • обновление контейнера не должно ломать сетевой контур и правила firewall

В простейшей конфигурации контейнер приложения слушает внутренний порт (например, 3000), а Nginx – 443 на хосте и проксирует внутрь.

Шаг 4. Секреты и конфигурация: где хранить переменные

Причина многих «у меня работает» – разные переменные окружения и конфиги. В схеме с Docker Compose удобен разделенный подход:

  • .env с секретами хранится только на сервере (и в защищенном хранилище секретов, если оно есть); в репозиторий не попадает
  • Тег образа передается отдельно (через переменную в CI) и не смешивается с секретами

На сервере создается файл, например /opt/app/app.env, который содержит только настройки окружения (строки вида KEY=VALUE). Этот файл подхватывается Docker Compose через env_file.

Шаг 5. SSH‑ключ для деплоя из CI

CI должен подключаться к VPS/VDS без пароля. Для этого создается отдельная пара ключей (ed25519. и добавляется в ~/.ssh/authorized_keys пользователя deploy. Приватный ключ хранится в секретах CI (GitHub Secrets / GitLab CI Variables) и не попадает в репозиторий.

Практические ограничения, которые часто забываются:

  • ключ лучше ограничивать по source IP, если CI имеет фиксированные адреса (не всегда возможно)
  • в authorized_keys допускается добавление опций (no-port-forwarding, no-agent-forwarding), если не требуется проксирование
  • для деплоя обычно достаточно ограниченного набора команд, но принудительная команда (forced command) усложняет сценарии с миграциями и проверками

Шаг 6. docker-compose.yml: минимальный пример под деплой

В каталоге /opt/app размещается docker-compose.yml. Важно, чтобы образ ссылался на переменную тега – тогда деплой сводится к подстановке нового тега и перезапуску.

Пример (схематично):
services:
app:
image: ghcr.io/org/project:${APP_TAG}
env_file:
— ./app.env
restart: unless-stopped
ports:
— «127.0.0.1:3000:3000»
healthcheck:
test: [«CMD», «curl», «-fsS», «http://127.0.0.1:3000/health»]
interval: 10s
timeout: 3s
retries: 6

Контейнер пробрасывается только на localhost, чтобы внешняя поверхность атаки оставалась у Nginx. Healthcheck нужен для автоматизированной проверки после обновления.

Шаг 7. CI: сборка образа и деплой по SSH (пример на GitHub Actions)

Ниже – примерный workflow, который демонстрирует ключевую мысль: сборка происходит в CI, а сервер только тянет готовый образ и перезапускает сервис. Аналогичная логика реализуется в GitLab CI, Jenkins, TeamCity и других системах.

Пример .github/workflows/deploy.yml (схематично):
name: build-and-deploy
on:
push:
branches: [«main»]
jobs:
build:
runs-on: ubuntu-latest
steps:
— uses: actions/checkout@v4
— uses: docker/setup-buildx-action@v3
— name: Login to registry
run: echo «${REGISTRY_TOKEN}» | docker login ghcr.io -u «${REGISTRY_USER}» —password-stdin
— name: Build and push
run: |
TAG=${GITHUB_SHA}
docker build -t ghcr.io/org/project:${TAG} .
docker push ghcr.io/org/project:${TAG}
deploy:
needs: build
runs-on: ubuntu-latest
steps:
— name: Deploy via SSH
run: |
ssh -o StrictHostKeyChecking=no deploy@${SERVER_HOST} \
«cd /opt/app && export APP_TAG=${GITHUB_SHA} && docker compose pull && docker compose up -d»

В реальном проекте этот пример стоит дополнить как минимум:

  • тестами перед сборкой и пушем образа
  • логином в registry на сервере (один раз или на каждый деплой) – безопаснее через токен с минимальными правами
  • выделенным деплой‑скриптом на сервере, чтобы команда SSH оставалась короткой и одинаковой
  • проверкой здоровья после обновления и условным откатом
ЧИТАТЬ ТАКЖЕ:  Когда без музыкальной программы не обойтись: путеводитель по творческому инструментарию

Шаг 8. Проверка после релиза и откат

Без проверки деплой превращается в «быстро сломать». Минимально достаточная практика – health endpoint и автоматический откат на предыдущий тег при неуспехе проверки.

Один из рабочих вариантов на VPS: хранить текущий и предыдущий тег в файле, например /opt/app/deploy.state. При успешном запуске новый тег становится текущим, при ошибке – выполняется возврат. Пример логики (псевдоскрипт):

deploy.sh (схема):
set -e
cd /opt/app
NEW_TAG=«$1»
CURRENT_TAG=«$(cat ./current.tag 2>/dev/null || true)»
echo «$NEW_TAG» > ./current.tag
export APP_TAG=«$NEW_TAG»
docker compose pull
docker compose up -d
sleep 5
curl -fsS https://example.com/health

Если curl возвращает ошибку, скрипт завершится с ненулевым кодом. В таком случае в CI можно выполнить отдельную команду отката, которая вернет CURRENT_TAG и перезапустит Compose. Практика показывает, что даже такой «простой» откат экономит часы при неудачных релизах.

Отдельный нюанс: миграции базы данных. Если миграции несовместимы назад, откат «по образу» может не вернуть работоспособность. В критичных системах применяются совместимые миграции (expand/contract), либо вводится отдельный шаг «migrate» до переключения трафика.

Push или pull: два способа запускать деплой на собственном сервере

В примере выше используется push‑модель: CI подключается к серверу и инициирует обновление. Это популярный вариант, но у него есть ограничения (доступ по SSH снаружи, необходимость хранить ключ в CI).

Альтернатива – pull‑модель: на VPS/VDS работает агент (runner), который сам забирает задачу из CI и выполняет деплой локально. Преимущества – нет входящего SSH с «внешней стороны CI», проще ограничить сеть, удобнее интегрировать в закрытых контурах. Недостатки – на сервере появляется дополнительный компонент, который требуется обновлять и мониторить.

Выбор зависит от требований безопасности. Для публичных проектов с обычным периметром push‑модель через SSH остается наиболее простой в поддержке.

Минимальная гигиена безопасности для CI/CD на VPS

Автоматизация не должна превращать виртуальный сервер в «открытую дверь». В практических внедрениях полезно проверять следующие пункты:

  • Отдельный пользователь для деплоя, без интерактивной работы под root
  • SSH только по ключам, по возможности – ограничение AllowUsers и отключение password authentication
  • Ограничение прав ключа в authorized_keys (опции запрета форвардинга) – если функциональность не нужна
  • Раздельные секреты: токен registry с правом read/pull для сервера и отдельный токен с правом push для CI
  • Логи: журнал SSH‑логинов, логи Nginx, docker logs или централизованный сбор
  • Обновления: регулярные security‑апдейты ОС и Docker, плановый пересмотр версий образов

При выборе площадки под аренду VPS важно, чтобы сервер можно было быстро переустановить и восстановить «с нуля» по документации/скриптам. В этом смысле полезнее смотреть не на маркетинговые цифры, а на операционные возможности панели и сети – пример типовых требований обычно указывается на страницах провайдеров вроде VPS.house с возможностью аренды VPS в Москве, но конкретный выбор зависит от периметра задач, бюджета и требований к задержкам.

Что чаще всего ломает автоматический деплой (и как это лечится)

  • Образ «плывет» от сборки к сборке. Решение: фиксировать версии базовых образов и зависимостей, использовать lock‑файлы, не собирать «latest» без контроля
  • Секреты попадают в образ. Решение: передавать секреты через env_file/переменные на сервере, исключать .env через .dockerignore, не копировать секреты в Dockerfile
  • Нет health endpoint. Решение: добавлять минимальный /health (без зависимости от внешних сервисов) и проверять его после перезапуска
  • Конкурирующие деплои (два коммита подряд). Решение: включать блокировку на уровне CI (concurrency) или на уровне сервера (lock‑файл в deploy.sh)
  • Миграции выполняются «между делом». Решение: формализовать шаг миграций и сделать его предсказуемым (отдельная команда/контейнер)
  • Сервер превращается в «снежинку». Решение: документировать подготовку VPS или автоматизировать ее (Ansible/Terraform/скрипты), хранить конфигурацию инфраструктуры отдельно от приложения

Если Docker не подходит: короткая альтернатива без контейнеров

Иногда контейнеризация невозможна (наследуемая среда, ограничения политики, специфичное ПО). В этом случае задача «убрать ручные копирования» все равно решается, но артефактом становится архив или собранный каталог.

Рабочая минимальная схема:

  1. CI собирает артефакт (например, dist/ или бинарник), упаковывает в tar.gz и подписывает версию (коммит/тег)
  2. CI загружает артефакт на VPS по SSH в каталог releases/ (rsync/scp)
  3. На сервере переключается симлинк current → новый релиз
  4. systemd перезапускает сервис (или reload для Nginx/PHP-FPM)
  5. Проверяется /health и при проблеме симлинк возвращается на предыдущий релиз

По сравнению с контейнерами здесь выше риск несовпадения системных библиотек, поэтому особенно важно фиксировать версии рантайма и зависимостей и не «подкручивать» окружение на сервере вручную.

Чек‑лист: признак того, что «ручной деплой» действительно исчез

  • На VPS/VDS не выполняется сборка приложения «вручную» – на сервер попадает готовый артефакт
  • Любая выкладка привязана к коммиту/тегу и отражается в логах CI
  • Переменные окружения и секреты находятся в управляемом месте и не копируются между средами случайно
  • Откат выполняется за минуты и не требует реконструкции состояния
  • Развертывание нового сервера (после сбоя или миграции) описано и воспроизводимо

После внедрения CI/CD на собственном виртуальном сервере исчезает сама почва для споров в стиле «у меня работает»: одинаковые сборки, одинаковые шаги деплоя и понятный контроль версий заменяют ручные копирования и «магические» команды. Для проектов на VPS/VDS это часто оказывается самым быстрым способом сократить потери времени на релизах без перехода на управляемые PaaS‑платформы.

ОСТАВЬТЕ ОТВЕТ

Пожалуйста, введите ваш комментарий!
пожалуйста, введите ваше имя здесь