Ссылка скопирована

Автодеплой Docker-проектов на VPS через GitHub Actions и SSH

DonVardixDonVardix—

Введение

При развертывании приложений в Docker-контейнерах классический деплой с выполнением composer или npm прямо на хосте не применим — все зависимости и исполняемая среда изолированы внутри образов. Ручное подключение к серверу для каждого docker compose build отнимает время и создает риск прерывания работы соседних сервисов.

Что будет сделано:

  • Настроены права доступа пользователя deploy для управления демоном Docker без sudo;
  • Сгенерирован и подключен сервисный SSH-ключ для загрузки приватного кода из GitHub;
  • Настроен пайплайн GitHub Actions (appleboy/ssh-action) для атомарного обновления исходного кода, пересборки и бесшовного перезапуска контейнеров;
  • Разобраны тонкости выполнения миграций базы данных без TTY-терминала и автоматическая очистка неиспользуемых слоев образов.

Результат: надежный CI/CD пайплайн, где любой git push в ветку main автоматически собирает и обновляет Docker-контейнеры на VPS без необходимости ручного входа на сервер и без перезагрузки реверс-прокси Caddy.


1. Подготовка пользователя и директории

Все действия по деплою выполняются от имени непривилегированного пользователя deploy. Для управления контейнерами этот пользователь должен состоять в системной группе docker.

1.1. Проверка прав пользователя deploy

Если вы настраивали сервер по руководству Настройка VPS под мультипроектный Docker-стек с Caddy, пользователь уже полностью настроен. Если нет, выполните под root:

usermod -aG docker deploy

Переключитесь на пользователя deploy:

su - deploy

Проверьте доступ к сокету Docker без sudo:

docker ps

1.2. Подготовка каталога проекта

Создайте каталог для приложения внутри /var/www/apps:

mkdir -p /var/www/apps/example-app

Важно

Все файлы, папки проектов и команды деплоя должны принадлежать строго пользователю deploy:deploy. Не запускайте docker compose под root.

2. Настройка Deploy Key для репозитория

Чтобы сервер мог безопасно клонировать и стягивать приватный репозиторий с GitHub без использования личных паролей или токенов с избыточными правами, используется отдельный сервисный SSH-ключ.

2.1. Генерация ключа и настройка SSH config

Если вы уже создавали сервисный ключ ~/.ssh/github_deploy при первичной настройке VPS, этот шаг можно пропустить. Если ключа нет, сгенерируйте его под пользователем deploy:

ssh-keygen -t ed25519 -f ~/.ssh/github_deploy -C "deploy@vps"

Нажмите Enter на всех запросах парольной фразы. Настройте файл ~/.ssh/config:

Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/github_deploy
    IdentitiesOnly yes

Задайте строгие права доступа:

chmod 600 ~/.ssh/config ~/.ssh/github_deploy
chmod 644 ~/.ssh/github_deploy.pub

2.2. Привязка Deploy Key в GitHub

Выведите публичный ключ на экран:

cat ~/.ssh/github_deploy.pub
  1. Откройте репозиторий вашего проекта на GitHub.
  2. Перейдите в Settings → Deploy Keys → Add deploy key.
  3. В поле Title введите VPS Deploy Key.
  4. В поле Key вставьте скопированную строку (флаг Allow write access включать не нужно).
  5. Нажмите Add key.

Проверьте соединение с сервера:

ssh -T git@github.com

При первом подключении введите yes. Ответ Hi ...! You've successfully authenticated подтвердит готовность.


3. Первичное клонирование и первый запуск

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

Перейдите в директорию приложения:

cd /var/www/apps/example-app
git clone git@github.com:username/repository.git .

Создайте и заполните рабочий .env:

nano .env

Соберите и запустите контейнеры:

docker compose up -d --build

Убедитесь, что веб-сервис проекта успешно подключен к внешней сети web_gateway и отдает ответ через Caddy.


4. Секреты в GitHub Actions

Для удаленного подключения runner’а GitHub Actions к вашему серверу добавьте секреты в репозитории: Settings → Secrets and variables → Actions → New repository secret.

Добавьте 4 обязательных секрета:

  • HOST — публичный IP-адрес вашего VPS (<ip_сервера>);
  • USERNAME — имя пользователя деплоя (deploy);
  • KEY — приватный SSH-ключ для авторизации на сервере (содержимое локального файла ~/.ssh/id_ed25519);
  • PORT — порт SSH-сервера (по умолчанию 22 или кастомный).

5. Создание workflow деплоя

В корне локального репозитория создайте файл .github/workflows/deploy.yml:

name: Deploy Docker App

on:
  push:
    branches:
      - main

concurrency:
  group: deploy-docker-app
  cancel-in-progress: true

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.KEY }}
          port: ${{ secrets.PORT }}
          script: |
            set -e

            cd /var/www/apps/example-app

            # 1. Синхронизация репозитория
            git fetch origin main
            git reset --hard origin/main

            # 2. Пересборка и запуск контейнеров
            docker compose build
            docker compose up -d

            # 3. Выполнение миграций и сброс кеша
            docker compose exec -T app php artisan migrate --force
            docker compose exec -T app php artisan optimize:clear

            # 4. Очистка устаревших Docker-образов
            docker image prune -f

Шаги 1, 2 и 4 (синхронизация репозитория, запуск контейнеров и очистка старых образов) универсальны для любых Docker-приложений. Команды шага 3 адаптируются под ваш фреймворк или удаляются, если контейнеру не требуются миграции.

Обязательный флаг -T

В CI/CD сессиях SSH нет TTY-терминала. Если выполнить docker compose exec без флага -T, команда упадет с ошибкой «the input device is not a TTY». Флаг -T отключает псевдо-терминал и обязателен для запуска миграций и консольных утилит в автоматическом режиме.

Советы и практики

Инфраструктура автодеплоя полностью готова. Каждый коммит в ветку main запускает runner, который обновляет код, собирает новые слои образов и бесшовно перезапускает сервисы.

Чек-лист первого деплоя

  1. Внесите изменение в коде проекта локально и выполните коммит:
    git commit -am "test: deploy verification"
    git push origin main
  2. Откройте вкладку Actions в репозитории на GitHub и проследите за выполнением пайплайна.
  3. Убедитесь, что все шаги завершились с зеленым статусом.
  4. Откройте сайт в браузере и проверьте, что изменения применились. Перезапускать Caddy не требуется — он автоматически маршрутизирует трафик на поднятый контейнер.

Команды для других фреймворков

В зависимости от архитектуры приложения блок служебных команд в workflow адаптируется:

  • Чистый PHP / FrankenPHP:
    Миграций нет, шаг 3 не требуется — контейнер пересобирается и перезапускается автоматически.
  • Node.js / Next.js:
    Сборка происходит на этапе docker compose build внутри multi-stage Dockerfile, дополнительных команд exec не требуется.
  • Django / Python:
    docker compose exec -T app python manage.py migrate --noinput
    docker compose exec -T app python manage.py collectstatic --noinput
  • WordPress (Bedrock):
    docker compose exec -T app wp core update-db

Шпаргалка команд

  • Просмотр логов запущенного приложения:
    cd /var/www/apps/example-app && docker compose logs -f --tail=100
  • Мониторинг ресурсов контейнеров:
    docker stats
  • Проверка состояния контейнеров:
    docker compose ps
  • Ручной перезапуск сервисов:
    docker compose restart
  • Проверка подключения контейнера к сети прокси:
    docker network inspect web_gateway