Skip to main content

WordPress в GitOps с Bedrock на VPS HMS

Это руководство объясняет, как разместить сайт WordPress на VPS HostMyServers и управлять им в стиле GitOps с помощью Bedrock — шаблона (boilerplate) WordPress от Roots. Ваш Git-репозиторий становится источником истины: ядро WordPress, плагины и темы декларируются в коде, версионируются, а затем автоматически развёртываются на вашем VPS при каждом git push.

VPS даёт вам root-доступ, SSH и полный контроль над стеком (Nginx, PHP-FPM, MariaDB), необходимый для такого способа развёртывания, — то, что не позволяет сделать shared-хостинг (виртуальный хостинг).

Почему Bedrock?

Обычный WordPress смешивает код (ядро, плагины, темы), конфигурацию (wp-config.php) и данные (загрузки) в одной папке и обновляется из панели администратора. Поэтому его сложно версионировать и воспроизводить. Bedrock даёт:

  • Composer для управления ядром WordPress, плагинами и темами как зависимостями (версии фиксируются в composer.lock)
  • Конфигурацию через переменные окружения (файл .env) без секретов в Git
  • Файлы конфигурации для каждого окружения (разработка, staging, продакшн)
  • Более безопасную структуру каталогов: сервер отдаёт наружу только папку web/
  • Отключение изменений из панели администратора в продакшне (DISALLOW_FILE_MODS)

Заказать VPS HostMyServers​

Это руководство рассчитано на VPS HostMyServers. Выберите тариф в зависимости от вашего трафика:

  • VPS NVMe — отличное соотношение цены и качества, идеально для старта сайта-визитки или блога
  • VPS Performance — рекомендуется для интернет-магазинов на WooCommerce и сайтов с высоким трафиком
ИспользованиеvCPURAMХранилище
Сайт-визитка / блог1-22 ГБ20 ГБ NVMe
Сайт со средним трафиком, несколько сайтов2-44 ГБ40 ГБ NVMe
WooCommerce / высокий трафик4+8 ГБ+80 ГБ NVMe+

При заказе выберите Ubuntu 24.04 LTS или Debian 12 в качестве операционной системы. После доставки VPS IP-адрес и root-данные для входа будут отправлены вам по электронной почте и доступны в вашем личном кабинете HostMyServers.

Более крупные проекты

Для нескольких сайтов с высоким трафиком та же процедура применима к выделенным серверам Eco и Performance.

Принцип GitOps применительно к WordPress​

ЭлементГде живётВерсионируется в Git?
Ядро WordPress, публичные плагины и темыcomposer.json + composer.lockДа (декларация + точные версии)
Тема и плагины, разработанные вамиweb/app/themes/, web/app/plugins/Да (исходный код)
Конфигурация (константы, окружения)config/Да
Секреты (пароли, ключи, соли).env на каждом сервереНет
Медиафайлы (загрузки)web/app/uploads/ на сервереНет (нужно резервировать)
Контент (записи, страницы, настройки)База данных MySQL/MariaDBНет (нужно резервировать)

Жизненный цикл выглядит так:

  1. Вы изменяете код или обновляете зависимость локально
  2. Коммитите и отправляете (push) изменения в ветку main
  3. CI-пайплайн устанавливает зависимости и разворачивает новый релиз на сервере
  4. В случае проблемы вы возвращаетесь к предыдущему релизу (git revert или переключение символической ссылки)
Что остаётся вне Git

База данных и медиафайлы — это данные, а не код. Git ими не управляет: настройте регулярное резервное копирование (wp db export, резервная копия папки shared/uploads, снапшоты VPS).

Предварительные требования​

На вашей машине разработки:

  • Git
  • PHP ≥ 8.1 (рекомендуется 8.3) и Composer 2
  • Аккаунт GitHub (или GitLab) для размещения репозитория

На вашем VPS HMS (Ubuntu 24.04 LTS / Debian 12):

  • SSH-доступ как root или пользователь с правами sudo
  • Защищённый VPS (пользователь без root, SSH, файрвол): см. Защита сервера
  • Доменное имя, чья DNS-запись A указывает на IP-адрес VPS
Composer не нужен на продакшне

В этом руководстве composer install выполняется CI-пайплайном. VPS получает только уже готовые файлы: ему не нужны ни Composer, ни Git, ни доступ к репозиториям пакетов.

Шаг 1: создать проект Bedrock локально​

  1. Создайте проект:

    composer create-project roots/bedrock mon-site
    cd mon-site
  2. Изучите структуру каталогов:

    mon-site/
    ├── composer.json # Ядро WordPress, плагины и темы декларируются здесь
    ├── composer.lock # Точные установленные версии
    ├── .env.example # Шаблон конфигурации
    ├── wp-cli.yml # Указывает WP-CLI, где находится WordPress (web/wp)
    ├── config/
    │ ├── application.php # Основная конфигурация (заменяет wp-config.php)
    │ └── environments/
    │ ├── development.php
    │ └── staging.php
    ├── vendor/ # Зависимости Composer (не версионируется)
    └── web/ # Корень веб-сервера (document root)
    ├── app/ # Аналог wp-content
    │ ├── mu-plugins/
    │ ├── plugins/
    │ ├── themes/
    │ └── uploads/
    ├── wp/ # Ядро WordPress (не версионируется)
    ├── wp-config.php
    └── index.php
    info

    В Bedrock панель администратора находится по адресу https://your-domain.com/wp/wp-admin, а wp-content заменён на web/app.

  3. Создайте локальный файл .env на основе шаблона:

    cp .env.example .env
    .env
    DB_NAME='wordpress_dev'
    DB_USER='wordpress_user'
    DB_PASSWORD='local_password'
    DB_HOST='localhost'
    DB_PREFIX='wp_'

    WP_ENV='development'
    WP_HOME='http://mon-site.test'
    WP_SITEURL="${WP_HOME}/wp"

    AUTH_KEY='generateme'
    SECURE_AUTH_KEY='generateme'
    LOGGED_IN_KEY='generateme'
    NONCE_KEY='generateme'
    AUTH_SALT='generateme'
    SECURE_AUTH_SALT='generateme'
    LOGGED_IN_SALT='generateme'
    NONCE_SALT='generateme'
  4. Сгенерируйте уникальные ключи и соли (нужно делать для каждого окружения):

    for k in AUTH_KEY SECURE_AUTH_KEY LOGGED_IN_KEY NONCE_KEY AUTH_SALT SECURE_AUTH_SALT LOGGED_IN_SALT NONCE_SALT; do
    echo "$k='$(openssl rand -base64 48 | tr -d '\n')'"
    done

    Скопируйте результат в ваш .env вместо строк generateme. Также можно воспользоваться генератором от Roots.

Локальное окружение

Чтобы запустить сайт локально, используйте любой удобный инструмент (DDEV, Lando, Laravel Valet, Docker…), указав в качестве корня веб-сервера папку web/.

Шаг 2: управление плагинами и темами через Composer​

Bedrock уже настроен для работы с WPackagist — Composer-зеркалом всех плагинов и тем из официального каталога WordPress.org.

  1. Установите плагин или тему:

    composer require wpackagist-plugin/wordpress-seo
    composer require wpackagist-plugin/wp-mail-smtp
    composer require wpackagist-theme/twentytwentyfive

    Имя пакета соответствует slug на WordPress.org (https://wordpress.org/plugins/<slug>/).

  2. Обновите зависимости:

    # Обновить всё согласно ограничениям composer.json
    composer update

    # Обновить только ядро WordPress
    composer update roots/wordpress --with-all-dependencies

    # Посмотреть доступные обновления
    composer outdated
  3. Удалите плагин:

    composer remove wpackagist-plugin/wp-mail-smtp
Всегда коммитьте composer.lock

Именно файл composer.lock гарантирует, что на продакшне установятся точно те же версии, что были протестированы локально. Он всегда должен быть версионирован.

Платные или приватные плагины​

Платные плагины отсутствуют на WPackagist. Есть два варианта:

  • Рекомендуется: использовать Composer-репозиторий, предоставляемый разработчиком плагина (ACF Pro, Gravity Forms, WPML… предлагают такой), с ключом аутентификации, хранящимся в auth.json (не версионируется) и в секретах вашего CI.

  • Альтернатива: версионировать плагин напрямую в репозитории, добавив исключение в .gitignore:

    .gitignore
    web/app/plugins/*
    !web/app/plugins/.gitkeep
    !web/app/plugins/mon-plugin-premium

Ваша собственная тема (в web/app/themes/) версионируется по умолчанию.

Шаг 3: настройка конфигурации​

Общая конфигурация находится в config/application.php. Переопределения для каждого окружения — в config/environments/<WP_ENV>.php. По умолчанию Bedrock:

  • отключает редактор файлов и установку/обновление плагинов из панели администратора (DISALLOW_FILE_MODS) в продакшне
  • разрешает их в development
  • скрывает ошибки PHP в продакшне

Такое поведение — суть GitOps: в продакшне любое изменение кода проходит через Git. Если плагин показывает «доступно обновление», обновите его через Composer локально, протестируйте, а затем закоммитьте.

Пример добавления константы для всех окружений:

config/application.php
Config::define('WP_POST_REVISIONS', 10);
Config::define('WP_MEMORY_LIMIT', '256M');

Шаг 4: инициализировать Git-репозиторий​

Bedrock поставляется с готовым .gitignore: vendor/, web/wp/, плагины, установленные через Composer, загрузки и файл .env игнорируются.

git init -b main
git add .
git commit -m "Initial Bedrock project"
git remote add origin git@github.com:your-account/mon-site.git
git push -u origin main

Убедитесь, что ни один секрет не версионируется:

git ls-files | grep -E '(^|/)\.env$|auth\.json' || echo "OK: секретов в репозитории нет"

Шаг 5: подготовка VPS HMS​

Подключиться к VPS​

Подключитесь с помощью IP-адреса и учётных данных, полученных при доставке сервера:

ssh root@vps_ip_address

Установить Nginx, PHP-FPM и MariaDB​

sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx mariadb-server \
php-fpm php-mysql php-curl php-gd php-intl php-mbstring php-xml php-zip php-imagick
php -v

Ubuntu 24.04 устанавливает PHP 8.3 (Debian 12: PHP 8.2). В дальнейшем в руководстве заменяйте php8.3-fpm на версию, которую показывает php -v. Для защиты MariaDB следуйте руководству Установка и защита MariaDB.

Если файрвол UFW активен, откройте веб-порты:

sudo ufw allow 'Nginx Full'

Создать пользователя для развёртывания и структуру каталогов​

Пайплайн будет подключаться под отдельным пользователем deploy. Код принадлежит deploy и доступен PHP (www-data) только для чтения; только папка загрузок доступна PHP для записи.

sudo adduser --disabled-password --gecos "" deploy
sudo usermod -aG www-data deploy

sudo mkdir -p /var/www/mon-site/{releases,shared/uploads}
sudo chown -R deploy:www-data /var/www/mon-site
sudo chown -R www-data:www-data /var/www/mon-site/shared/uploads
sudo chmod 2775 /var/www/mon-site/shared/uploads

Структура каталогов развёртывания будет следующей:

/var/www/mon-site/
├── current -> releases/<sha> # Символическая ссылка на активный релиз
├── releases/
│ ├── 3f2a9c1.../ # Один релиз на каждый развёрнутый коммит
│ └── 8be41d0.../
└── shared/
├── .env # Конфигурация продакшна (вне Git)
└── uploads/ # Медиафайлы, сохраняющиеся между релизами

Создать базу данных​

sudo mysql -u root -p
CREATE DATABASE wordpress_prod DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wordpress_user'@'localhost' IDENTIFIED BY 'secure_password';
GRANT ALL PRIVILEGES ON wordpress_prod.* TO 'wordpress_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Создать файл .env для продакшна​

sudo -u deploy nano /var/www/mon-site/shared/.env
/var/www/mon-site/shared/.env
DB_NAME='wordpress_prod'
DB_USER='wordpress_user'
DB_PASSWORD='secure_password'
DB_HOST='localhost'
DB_PREFIX='wp_'

WP_ENV='production'
WP_HOME='https://your-domain.com'
WP_SITEURL="${WP_HOME}/wp"

# Ключи и соли, сгенерированные командой openssl из шага 1
AUTH_KEY='...'
SECURE_AUTH_KEY='...'
LOGGED_IN_KEY='...'
NONCE_KEY='...'
AUTH_SALT='...'
SECURE_AUTH_SALT='...'
LOGGED_IN_SALT='...'
NONCE_SALT='...'
sudo chown deploy:www-data /var/www/mon-site/shared/.env
sudo chmod 640 /var/www/mon-site/shared/.env

Настроить Nginx​

Корень веб-сервера указывает на current/web: код, vendor/ и .env никогда не отдаются наружу.

/etc/nginx/sites-available/mon-site
server {
listen 80;
server_name your-domain.com www.your-domain.com;

root /var/www/mon-site/current/web;
index index.php;

client_max_body_size 64M;

location / {
try_files $uri $uri/ /index.php?$args;
}

# Запретить выполнение PHP в папке загрузок
location ~* ^/app/uploads/.*\.php$ {
deny all;
}

location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
# Разрешать символическую ссылку "current" при каждом запросе
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
}

location ~ /\. {
deny all;
}
}
sudo ln -s /etc/nginx/sites-available/mon-site /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
$realpath_root

Использование $realpath_root (вместо $document_root) гарантирует, что PHP-FPM загружает файлы нового релиза сразу после переключения ссылки current, не отдавая закэшированные старые пути.

Затем включите HTTPS, например с помощью Certbot (см. также Установка SSL-сертификата):

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com -d www.your-domain.com

Разрешить перезагрузку PHP-FPM​

Пайплайн перезагружает PHP-FPM после каждого развёртывания, чтобы сбросить кэш OPcache. Разрешите для пользователя deploy только эту команду:

echo 'deploy ALL=(root) NOPASSWD: /usr/bin/systemctl reload php8.3-fpm' | sudo tee /etc/sudoers.d/deploy
sudo chmod 440 /etc/sudoers.d/deploy
sudo visudo -c

Создать SSH-ключ для развёртывания​

На вашей машине сгенерируйте отдельную пару ключей для пайплайна:

ssh-keygen -t ed25519 -C "github-actions-deploy" -f ./deploy_key -N ""

Добавьте публичный ключ на VPS:

sudo mkdir -p /home/deploy/.ssh
sudo nano /home/deploy/.ssh/authorized_keys # вставьте содержимое deploy_key.pub
sudo chown -R deploy:deploy /home/deploy/.ssh
sudo chmod 700 /home/deploy/.ssh && sudo chmod 600 /home/deploy/.ssh/authorized_keys

Получите SSH-отпечаток VPS (чтобы избежать атаки типа man-in-the-middle):

ssh-keyscan -H vps_ip_address

Шаг 6: автоматизировать развёртывание с GitHub Actions​

Объявить секреты​

В вашем репозитории GitHub перейдите в Settings → Secrets and variables → Actions и создайте:

СекретЗначение
SSH_HOSTIP-адрес вашего VPS HMS
SSH_USERdeploy
SSH_PRIVATE_KEYСодержимое файла deploy_key (приватный ключ)
SSH_KNOWN_HOSTSРезультат команды ssh-keyscan

После этого удалите файлы deploy_key и deploy_key.pub со своей машины.

Создать workflow​

.github/workflows/deploy.yml
name: Deploy

on:
push:
branches: [main]
workflow_dispatch:

concurrency:
group: production
cancel-in-progress: false

jobs:
deploy:
runs-on: ubuntu-latest
environment: production
env:
BASE_DIR: /var/www/mon-site
TARGET: ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}
steps:
- uses: actions/checkout@v4

- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
tools: composer:v2

- name: Установить зависимости
run: composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader

- name: Настроить SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
chmod 600 ~/.ssh/id_ed25519

- name: Отправить релиз
run: |
rsync -az --delete \
--exclude='.git' --exclude='.github' \
--exclude='.env' --exclude='web/app/uploads' \
./ "$TARGET:$BASE_DIR/releases/$GITHUB_SHA/"

- name: Активировать релиз
run: |
ssh "$TARGET" bash -s -- "$BASE_DIR" "$GITHUB_SHA" <<'EOF'
set -euo pipefail
BASE_DIR="$1"
RELEASE="$BASE_DIR/releases/$2"

# Связать общую конфигурацию и медиафайлы
ln -sfn "$BASE_DIR/shared/.env" "$RELEASE/.env"
rm -rf "$RELEASE/web/app/uploads"
ln -sfn "$BASE_DIR/shared/uploads" "$RELEASE/web/app/uploads"

# Атомарное переключение ссылки "current"
ln -sfn "$RELEASE" "$BASE_DIR/current.tmp"
mv -Tf "$BASE_DIR/current.tmp" "$BASE_DIR/current"

# Сбросить OPcache
sudo systemctl reload php8.3-fpm

# Сохранить 5 последних релизов
cd "$BASE_DIR/releases"
ls -1t | tail -n +6 | xargs -r rm -rf
EOF

Закоммитьте и отправьте этот файл: первое развёртывание запустится автоматически. Следите за его выполнением на вкладке Actions репозитория.

Вариант с GitLab

Тот же принцип применим с GitLab CI: образ composer:2 для composer install, затем rsync и ssh с защищёнными переменными CI/CD вместо секретов GitHub.

Первый запуск​

После успешного первого развёртывания установите WordPress. Либо через браузер по адресу https://your-domain.com/wp/wp-admin/install.php, либо с помощью WP-CLI на сервере:

cd /var/www/mon-site/current
sudo -u www-data wp core install \
--url="https://your-domain.com" \
--title="Your site title" \
--admin_user="admin" \
--admin_password="StrongPassword123!" \
--admin_email="your@email.com"

См. руководство Установка WordPress с WP-CLI для установки WP-CLI на сервере.

XML-карта сайта и SEO​

WordPress нативно генерирует карту сайта (sitemap) по адресу https://your-domain.com/wp-sitemap.xml. С Bedrock и приведённой выше конфигурацией Nginx (try_files ... /index.php?$args) она работает без дополнительных настроек. Проверьте это:

curl -sI https://your-domain.com/wp-sitemap.xml | head -n 1

Если вы устанавливаете SEO-плагин через Composer (например, composer require wpackagist-plugin/wordpress-seo), он заменяет нативную карту сайта своей собственной (/sitemap_index.xml для Yoast SEO). Карта сайта по-прежнему генерируется динамически из базы данных: версионировать в Git нечего.

После этого не забудьте:

  • Указать URL карты сайта в Google Search Console и Bing Webmaster Tools
  • Проверить, что в продакшне не отмечена галочка Настройки → Чтение → Видимость для поисковых систем
Staging не индексируется

Bedrock включает mu-plugin bedrock-disallow-indexing: на окружении, где WP_ENV равен staging или development, сайт просит поисковые системы не индексировать его (DISALLOW_INDEXING). Таким образом, в результатах поиска появляется только продакшн.

Повседневная работа​

Обновление WordPress и плагинов​

composer update
# Протестировать сайт локально, затем:
git add composer.json composer.lock
git commit -m "chore: update WordPress and plugins"
git push

Развёртывание происходит автоматически. Чтобы получать уведомления о новых версиях, включите Dependabot (экосистема composer) или Renovate в репозитории: они будут открывать pull request'ы с обновлениями, которые останется только одобрить.

Использование ветки staging​

Чтобы проверять изменения перед продакшном, создайте второе окружение (поддомен staging.your-domain.com, другая папка, другая база данных, WP_ENV='staging') и продублируйте workflow, запуская его для ветки staging. Файл config/environments/staging.php при этом применится автоматически.

Откат назад (rollback)​

Метод GitOps (рекомендуется): отмените проблемный коммит, пайплайн заново развернёт предыдущее состояние.

git revert <commit_sha>
git push

Аварийный метод: вручную переключите ссылку current на предыдущий релиз прямо на сервере.

cd /var/www/mon-site
ls -1t releases/ # найти предыдущий релиз
ln -sfn "$PWD/releases/<previous_sha>" current.tmp && mv -Tf current.tmp current
sudo systemctl reload php8.3-fpm
caution

Откат кода не восстанавливает базу данных. Если обновление изменило схему базы, восстановите также SQL-резервную копию, сделанную до развёртывания.

Перенос существующего сайта WordPress на Bedrock​

  1. Составьте список плагинов и тем, установленных на старом сайте: wp plugin list и wp theme list

  2. Добавьте их в проект Bedrock с помощью composer require wpackagist-plugin/<slug> (а собственные разработки версионируйте напрямую)

  3. Скопируйте старую папку wp-content/uploads/ в /var/www/mon-site/shared/uploads/

  4. Импортируйте базу данных, затем исправьте пути к медиафайлам, которые меняются с wp-content на app:

    cd /var/www/mon-site/current
    sudo -u www-data wp db import /path/to/backup.sql
    sudo -u www-data wp search-replace '/wp-content/uploads' '/app/uploads' --all-tables
  5. Если префикс таблиц отличается от wp_, скорректируйте DB_PREFIX в файле .env

tip

Перед любым wp search-replace сделайте пробный запуск с --dry-run, чтобы проверить количество замен.

Рекомендации​

  • Никогда не изменяйте файлы напрямую на сервере: всё проходит через Git
  • Всегда коммитьте composer.lock и фиксируйте критичные версии в composer.json
  • Держите секреты вне репозитория (.env, auth.json) и используйте секреты CI
  • Защитите ветку main (обязательное ревью pull request'ов)
  • Регулярно делайте резервные копии базы данных и папки shared/uploads, дополняя их снапшотами/резервными копиями вашего VPS
  • Сначала развёртывайте на staging-окружении

При возникновении проблем​

  • Белая страница или ошибка 500: проверьте логи sudo tail -f /var/log/nginx/error.log и /var/log/php8.3-fpm.log, а также наличие ссылки .env в активном релизе
  • Ошибка подключения к базе данных: проверьте учётные данные в файле shared/.env
  • После развёртывания отображается старая версия: убедитесь, что PHP-FPM действительно был перезагружен и что Nginx использует $realpath_root
  • Не удаётся загрузить медиафайлы: проверьте, что shared/uploads принадлежит www-data и что ссылка web/app/uploads действительно указывает на неё
  • Пайплайн не может подключиться по SSH: проверьте секреты SSH_PRIVATE_KEY и SSH_KNOWN_HOSTS и протестируйте ssh deploy@vps_ip_address со своей машины
  • Невозможно установить плагины из панели администратора: это нормально в продакшне (DISALLOW_FILE_MODS), используйте Composer