Top.Mail.Ru
OSEN-НИЙ SAAALEСкидка 50% на виртуальный хостинг и VDS
до 30.11.2025 Подробнее
Выберите продукт

CI/CD для PHP на VDS: GitHub Actions, rsync/SSH и .env секреты

Практический конвейер CI/CD для PHP на VDS: настройка сервера, GitHub Actions, деплой rsync по SSH, безопасная работа с .env, релизы с симлинком current, быстрый откат, миграции, OPCache и защита пайплайна. Подходит для Laravel, Symfony, WordPress и самописных проектов.
CI/CD для PHP на VDS: GitHub Actions, rsync/SSH и .env секреты

Зачем PHP-проекту CI/CD на VDS

Когда проект живет на VDS, главная боль — стабильный и повторяемый деплой без правок «на проде». CI/CD снимает риски: сборка и проверки идут в чистой среде, на сервер попадает только то, что прошло пайплайн, а переключение версий делается атомарно. Для PHP это критично: кэш опкода, миграции БД, права на файлы, секреты в .env — всё должно быть под контролем и воспроизводимо.

В этой статье показываю рабочую схему: GitHub Actions собирает приложение, деплоит на VDS через rsync/SSH, на сервере формируются релизы и символическая ссылка current, секреты не утекут, а откат возможен одной командой.

Архитектура пайплайна

Базовая схема простая:

  • GitHub Actions — сборка, тесты, подготовка артефактов, установка SSH-ключа и деплой.
  • rsync по SSH — быстрая передача только изменившихся файлов на VDS.
  • Каталоги релизов на сервере: /var/www/app/releases/<timestamp> и симлинк /var/www/app/current, который указывает на активную версию.
  • Секреты в .env живут вне релизов (в shared) и подлинковываются в каждый релиз.

Смысл релизов в том, чтобы переключение на новую версию происходило мгновенно, без состояния «полусобранного» деплоя. А откат — это просто смена симлинка на предыдущую папку.

Структура каталогов релизов на сервере: releases, current и общий .env

Подготовка VDS

Системный пользователь и директории

Создайте отдельного пользователя для деплоя и структуру директорий (пример: пользователь deploy, корень приложения /var/www/app):

adduser --disabled-password --gecos "" deploy
mkdir -p /var/www/app/releases
mkdir -p /var/www/app/shared
chown -R deploy:www-data /var/www/app
chmod 2775 /var/www/app /var/www/app/releases /var/www/app/shared

Бит 2 (setgid) на директориях гарантирует, что новые файлы получают группу www-data. Это снижает проблемы прав при работе PHP-FPM.

Где хранить .env

Файл .env храните в /var/www/app/shared/.env с ограниченными правами:

touch /var/www/app/shared/.env
chown deploy:www-data /var/www/app/shared/.env
chmod 640 /var/www/app/shared/.env

В каждом релизе он должен быть подлинкован на этот файл: releases/<timestamp>/.env -> ../shared/.env. Так секреты не копируются по каждому релизу и остаются в одном месте.

Веб-сервер

Корень сайта указывайте на /var/www/app/current/public (если это Laravel/Symfony) или просто /var/www/app/current для классических PHP-проектов. После переключения симлинка перезапуск веб-сервера не требуется, но полезно перезагрузить PHP-FPM для сброса опкода.

SSH-ключи и безопасность

Сгенерируйте отдельную пару ключей под деплой, публичный ключ положите в ~deploy/.ssh/authorized_keys. В sshd_config включите вход только по ключам и ограничьте доступ по пользователю:

PermitRootLogin no
PasswordAuthentication no
AllowUsers deploy

Перезапустите SSH и проверьте вход. На стороне GitHub Actions приватный ключ сохраните как секрет репозитория.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Секреты и .env: два практичных паттерна

1) Сервер — «источник истины»

Простой и безопасный подход: .env создаётся и редактируется только на сервере. Пайплайн никогда не видит содержимое секретов. В момент деплоя мы лишь подлинковываем файл в новый релиз. Этот вариант минимизирует риски утечки через логи CI/CD.

2) .env собирается в пайплайне

Если нужно управлять конфигурацией централизованно, собирайте .env из секретов GitHub Actions и отправляйте его в shared. Важно: не печатать содержимое файла в логи и не коммитить его. Запрещаем историю, даём файлу минимальные права и сразу стираем локальную копию после отправки.

Пример workflow GitHub Actions

Сценарий ниже делает следующее: чекаут кода, подготовка PHP, установка зависимостей, формирование номера релиза, загрузка кода на VDS по rsync, установка зависимостей уже на сервере, прогрев кэшей, атомарное переключение current, очистка старых релизов.

name: ci-cd-php

on:
  push:
    branches: [ "main" ]
  workflow_dispatch: {}

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      SSH_HOST: ${{ secrets.SSH_HOST }}
      SSH_PORT: ${{ secrets.SSH_PORT }}
      SSH_USER: ${{ secrets.SSH_USER }}
      REMOTE_DIR: /var/www/app
      PHP_VERSION: '8.2'
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ env.PHP_VERSION }}
          extensions: mbstring, intl, zip, bcmath, pdo_mysql
          coverage: none

      - name: Cache Composer
        uses: actions/cache@v4
        with:
          path: ~/.composer/cache
          key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}

      - name: Install dependencies (dev)
        run: composer install --no-interaction --prefer-dist

      # Если нужно собирать фронтенд, добавьте шаги npm ci / build

      - name: Create release id
        run: |
          REL=$(date +%Y%m%d%H%M%S)
          echo "RELEASE=$REL" >> $GITHUB_ENV

      - name: Prepare SSH key
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_KEY }}" > ~/.ssh/id_deploy
          chmod 600 ~/.ssh/id_deploy
          ssh-keyscan -p $SSH_PORT $SSH_HOST >> ~/.ssh/known_hosts

      # Если .env собирается в CI, раскомментируйте блок ниже
      # - name: Build .env from secrets
      #   run: |
      #     cat > .env << 'EOF'
      #     APP_ENV=production
      #     APP_DEBUG=false
      #     APP_KEY=${{ secrets.APP_KEY }}
      #     DB_HOST=${{ secrets.DB_HOST }}
      #     DB_DATABASE=${{ secrets.DB_NAME }}
      #     DB_USERNAME=${{ secrets.DB_USER }}
      #     DB_PASSWORD=${{ secrets.DB_PASS }}
      #     EOF

      - name: Rsync code to release
        run: rsync -az --delete --exclude ".git" --exclude ".github" --exclude "node_modules" --exclude "vendor" -e "ssh -i ~/.ssh/id_deploy -p $SSH_PORT" ./ $SSH_USER@$SSH_HOST:$REMOTE_DIR/releases/$RELEASE/

      # Если .env собирается в CI, отправим его на сервер и удалим локально
      # - name: Upload .env to shared
      #   run: |
      #     rsync -az -e "ssh -i ~/.ssh/id_deploy -p $SSH_PORT" .env $SSH_USER@$SSH_HOST:$REMOTE_DIR/shared/.env
      #     shred -u .env

      - name: Remote install and switch
        run: ssh -i ~/.ssh/id_deploy -p $SSH_PORT $SSH_USER@$SSH_HOST "cd $REMOTE_DIR/releases/$RELEASE && ln -sfn $REMOTE_DIR/shared/.env .env && composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader && php artisan config:cache || true && php artisan route:cache || true && php artisan view:cache || true && ln -sfn $REMOTE_DIR/releases/$RELEASE $REMOTE_DIR/current && chown -h deploy:www-data $REMOTE_DIR/current && sudo systemctl reload php8.2-fpm || true"

      - name: Cleanup old releases (keep 5)
        run: ssh -i ~/.ssh/id_deploy -p $SSH_PORT $SSH_USER@$SSH_HOST "cd $REMOTE_DIR/releases && ls -1t | tail -n +6 | xargs -r rm -rf"

Пояснения:

  • Зависимости Composer ставим на сервере внутри релиза, чтобы не тянуть vendor по сети. Убедитесь, что Composer установлен на VDS.
  • Опциональные команды artisan помечены || true, чтобы не обрушить деплой на проектах без Laravel. Подставьте свои команды сборки/кэшей (Symfony, WordPress MU-plugins и т.п.).
  • Переключение current делается после сборки, что обеспечивает атомарность.

Обзор workflow GitHub Actions для деплоя PHP через SSH и rsync

Релизы и откаты

Каждая версия создаётся в отдельной папке, затем обновляется симлинк current. Если релиз неудачный, откат — это выбор предыдущей папки и смена ссылки:

cd /var/www/app
PREV=$(ls -1t releases | sed -n '2p')
ln -sfn /var/www/app/releases/$PREV /var/www/app/current
sudo systemctl reload php8.2-fpm

Держите 5–10 релизов для отладки. При миграции проекта без простоя может пригодиться чек-лист из статьи про перенос сайтов: переезд без простоя.

Права, группы и владельцы

Типичные ошибки деплоя связаны с правами. Рекомендации:

  • deploy должен быть владельцем файлов, а группа — www-data (или группа PHP-FPM). Директории с setgid.
  • Каталоги для записываемых данных (например, storage у Laravel, wp-content/uploads у WordPress) вынесите в shared и подлинкуйте в релиз.
  • Не давайте 777; достаточно 750/640 при корректных владельцах.

Оптимизация rsync

rsync хорош тем, что передаёт только изменения и сохраняет права. Полезные флаги:

  • -a — архивный режим (права, время, симлинки);
  • -z — сжатие по сети;
  • --delete — чистит файлы, удалённые в репозитории (безопаснее применять в каталоге релиза, а не в current);
  • --info=progress2 — для диагностики при ручных прогонах.

Сброс OPCache и перезагрузка PHP-FPM

После переключения релиза полезно сбросить коды в OPCache. Самый простой способ — мягкая перезагрузка PHP-FPM. Для высокой нагрузки рассмотрите управляемый сброс через административный endpoint приложения (безопасно ограниченный) — но не держите публичных скриптов, сбрасывающих кэш, в веб-корне.

Миграции БД без простоя

Хорошая практика — выполнять миграции до переключения симлинка, если они обратно совместимы. Иначе используйте последовательность: включить режим обслуживания, применить миграции, прогреть кэши, переключить current, выключить режим обслуживания. Для Laravel это php artisan down/up и migrate --force.

Управление несколькими окружениями

Для staging и production создайте два деплоя: раздельные секреты, разные серверы/порты/пользователи, отдельные .env. В GitHub Actions удобно назначать environments с обязательным подтверждением релиза на проде. Теги (vX.Y.Z) можно использовать как триггер production-пайплайна.

Безопасность пайплайна

  • Храните приватный ключ деплоя только в секретах CI. Публичный — узкоспециализированный, отдельный от личных ключей.
  • Ограничьте пользователя deploy минимумом прав. Не выдавайте sudo без необходимости.
  • Проверяйте ключ хоста: ssh-keyscan добавляет отпечаток в known_hosts; фиксируйте его и не перезаписывайте без проверки.
  • Фильтруйте логи: не печатайте секреты, не включайте избыточный verbose там, где это может раскрыть конфиденциальные строки.

Диагностика и частые проблемы

  • Permission denied при записи — проверьте владельцев и группу, а также setgid на директориях.
  • No such file or directory .env — забудьте создать shared/.env или симлинк в релизе.
  • Class not found после деплоя — выполните composer dump-autoload -o внутри релиза и сбросьте OPCache.
  • 502/504 сразу после релиза — проверьте логи PHP-FPM и веб-сервера; возможно, кэш маршрутов несовместим с кодом или миграции не применены.
  • rsync: mkstemp failed — нет прав на каталог назначения или мало места на диске.

Вариант: деплой только артефактов

Если сборка тяжёлая (например, фронтенд), логичнее собирать артефакты в CI (включая vendor) и выкатывать их единым архивом. Тогда на сервере не нужен Composer/Node.js. Минус — больший трафик. Плюс — детерминированная сборка в чистой среде. Для фоновых очередей и воркеров посмотрите, как поднимать workers через systemd: очереди и Supervisor.

Резюме

CI/CD на VDS для PHP-проектов можно держать максимально прозрачным: GitHub Actions + rsync/SSH, релизы в отдельных директориях, симлинк current, аккуратная работа с .env и простой откат. Начните с минимальной схемы, добавьте миграции, кэши и проверки, и только потом усложняйте. Такая архитектура масштабируется от небольших сайтов до крупных приложений и не привязана к конкретному фреймворку.

Поделиться статьей

Вам будет интересно

mTLS для админок и API на VDS: клиентские сертификаты, ssl_verify_client и ротация OpenAI Статья написана AI Fastfox

mTLS для админок и API на VDS: клиентские сертификаты, ssl_verify_client и ротация

mTLS — способ ограничить доступ к админке и приватному API только доверенным клиентам. В статье: как поднять клиентский CA, выпуск ...
nftables на VDS: современный файрвол, базовые правила, persist и отладка OpenAI Статья написана AI Fastfox

nftables на VDS: современный файрвол, базовые правила, persist и отладка

Кратко о nftables на VDS: почему он удобнее iptables, как быстро собрать базовый файрвол для IPv4/IPv6, сохранить правила через /e ...
PgBouncer на VDS: пулы соединений PostgreSQL, конфиг, лимиты и метрики OpenAI Статья написана AI Fastfox

PgBouncer на VDS: пулы соединений PostgreSQL, конфиг, лимиты и метрики

Почему PgBouncer обязателен на нагруженном VDS с PostgreSQL: пулы соединений экономят память и ускоряют отклик. Разберём установку ...