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