TL;DR
- Bedrock просто реорганизует WordPress с Composer и конфигурацией на основе окружения. Ему не нужны Trellis, Ansible или VPS: Docker локально плюс shared-хостинг прекрасно работают.
- Нюанс, который нигде не описан: вы навсегда держите два отдельных файла
.env, потому чтоDB_HOST, работающий локально, никогда не сработает на продакшене, и наоборот. - Деплой через
git pull(vendor/,web/wp/,uploads/и.envостаются вне git), document root указывает наweb/, и важно понимать, чтоDISALLOW_FILE_MODSменяет в вашем процессе на продакшене.
Первый раз, когда я разворачивал WordPress Bedrock на shared-хостинге, я два часа потратил на ошибку подключения к базе данных, прежде чем понял, в чём дело. Все туториалы, которые я нашёл, показывали один и тот же .env файл. И все заканчивались на docker compose up. Ни один не упоминал, что значение DB_HOST, которое нужно локально, никогда не сработает на продакшене, и наоборот. Это два разных файла с двумя разными значениями, которыми вы управляете отдельно всегда. Это несложно, когда знаешь. Просто нигде не написано.
В этой статье — точная настройка WordPress Bedrock на Docker, которая используется на этом сайте: Docker Compose локально, Hostinger shared hosting на продакшене, деплой через git pull, без Trellis, без Ansible, без VPS. Три раздела, которых нет больше нигде: проблема двух .env, деплой на bare PHP без контейнеров, и что DISALLOW_FILE_MODS реально делает с рабочим процессом. Остальное — для полноты картины, но именно эти три раздела — причина существования статьи.

Что Bedrock на самом деле делает (и чего не делает)
Bedrock — это WordPress-шаблон от Roots, который реорганизует стандартную структуру, подтягивает WordPress core и плагины через Composer и заменяет wp-config.php конфигурацией на основе окружений. Всё. Он не управляет серверами, не запускает деплои и не требует Trellis, как бы это ни следовало из документации Roots.
Структура директорий, которая важна:
your-project/
config/
application.php # reads .env, defines WP constants
environments/
development.php # debug on, file mods allowed
production.php # debug off, DISALLOW_FILE_MODS
web/ # document root — point your server here
wp/ # WordPress core (Composer-installed)
app/ # wp-content equivalent
themes/
plugins/
mu-plugins/
uploads/
wp-config.php # minimal bootstrap, loads application.php
index.php
composer.json # pins WP core + plugins as packages
.env # never committedЧто не попадает в git: vendor/, web/wp/, web/app/uploads/ и .env. Всё остальное под контролем версий, включая composer.lock.
Ключевая деталь для shared-хостинга: document root должен указывать на web/, а не на корень репозитория. Если хостинг видит корень репо, посетители получат листинг директорий. О том, как настроить это на Hostinger, — в разделе про продакшен.
Что понадобится
- Docker Desktop или OrbStack (Mac) — OrbStack быстрее и легче на Apple Silicon
- Composer 2.x на хост-машине (только для первоначальной инициализации; PHP runtime берёт на себя Docker)
- Git-репозиторий на GitHub или GitLab
- SSH-доступ к shared-хостингу — Hostinger Business и выше включает SSH
- Базовое понимание
.envфайлов и работы в терминале
Настройка локального Docker Compose
Сначала инициализируем проект:
composer create-project roots/bedrock your-project
cd your-projectЗатем создаём docker-compose.yml. Вот что используется на этом сайте локально:
services:
app:
image: php:8.4-apache
platform: linux/amd64
volumes:
- .:/var/www/html
ports:
- "8880:80"
depends_on:
- db
command: >
bash -c "
apt-get update -qq &&
apt-get install -y -qq libpng-dev libjpeg-dev libzip-dev zip unzip &&
docker-php-ext-install pdo_mysql gd zip &&
sed -i 's|/var/www/html|/var/www/html/web|g' /etc/apache2/sites-enabled/000-default.conf &&
a2enmod rewrite &&
apache2-foreground
"
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:Несколько моментов, которые стоит проговорить явно. Строка platform: linux/amd64 избавляет от проблем совместимости ARM на Apple Silicon. Шаг docker-php-ext-install pdo_mysql не опционален — без него PHP вообще не умеет разговаривать с MySQL, и WordPress выдаст “Error establishing a database connection” независимо от того, что написано в .env. Переопределение document root указывает Apache на web/ согласно структуре Bedrock. Имя сервиса db — это то, что нужно писать в DB_HOST, не localhost и не 127.0.0.1. Это самая частая первая ошибка, поэтому ниже для неё отдельный раздел.
Запускаем стек:
docker compose up -dWP-CLI и Composer запускаем через контейнер. Если есть Makefile, эти обёртки экономят много времени:
.PHONY: up down shell wp composer logs
up:
docker compose up -d
down:
docker compose down
shell:
docker compose exec app bash
wp:
docker compose exec app wp $(CMD) --allow-root
composer:
docker compose exec app composer $(CMD)
logs:
docker compose logs -f appЗависимости устанавливаем внутри контейнера, а не на хост-машине — чтобы избежать конфликтов версий PHP:
make composer CMD="install"Затем устанавливаем WordPress:
make wp CMD="core install --url=http://localhost:8880 --title='My Site' --admin_user=admin --admin_password=admin [email protected]"Сайт доступен по адресу http://localhost:8880. Не монтируйте vendor/ и web/wp/ как volumes — они управляются Composer и не должны переопределяться файлами с хоста.
Проблема двух .env файлов
Этот раздел пропускают все туториалы. Локальный .env и продакшн-.env — полностью отдельные файлы с несовместимыми значениями. Ни один не коммитится в git. Управляете ими независимо.
Почему они не могут использовать одни и те же значения:
| Переменная | Локально (Docker Compose) | Продакшен (Hostinger) |
|---|---|---|
DB_HOST | db (имя сервиса Compose) | 127.0.0.1 или внутренний хост cPanel |
DB_NAME | wordpress | u336386_prod (с префиксом cPanel) |
DB_USER | wordpress | u336386_wp |
WP_HOME | http://localhost:8880 | https://yourdomain.com |
WP_SITEURL | http://localhost:8880/wp | https://yourdomain.com/wp |
WP_ENV | development | production |
DISALLOW_FILE_MODS | не задано (или false) | true |
DB_HOST=db работает локально, потому что Docker Compose создаёт приватную сеть, в которой сервисы находят друг друга по имени. За пределами этой сети db ни во что не резолвится. Если написать DB_HOST=localhost локально, WordPress попытается подключиться через Unix-сокет, которого внутри контейнера для MySQL-сервиса нет. Ошибка — “Error establishing a database connection” без каких-либо подробностей. Исправление — одно слово: заменить localhost на db.
Управляйте двумя файлами так: закоммитьте .env.example с placeholder-значениями и комментариями, объясняющими, что должно быть в каждой переменной для каждого окружения. Реальные значения храните в менеджере паролей. Никогда не кладите их в репозиторий, Slack или общий документ.
# .env.example
DB_NAME=''
DB_USER=''
DB_PASSWORD=''
# Local Docker: use 'db' (the Compose service name)
# Production: use 127.0.0.1 or the cPanel database host
DB_HOST=''
# Local: http://localhost:8880
# Production: https://yourdomain.com
WP_HOME=''
WP_SITEURL="${WP_HOME}/wp"
WP_ENV='development'
# Salts — generate at https://roots.io/salts.html
AUTH_KEY=''
SECURE_AUTH_KEY=''
LOGGED_IN_KEY=''
NONCE_KEY=''
AUTH_SALT=''
SECURE_AUTH_SALT=''
LOGGED_IN_SALT=''
NONCE_SALT=''Продакшен: bare PHP на Hostinger (без Docker)
Shared-хостинг не запускает Docker. Hostinger Business и выше даёт SSH, PHP 8.x, MySQL и Composer, но без container runtime. Это нормально. Bedrock — обычный PHP. Ему не нужны контейнеры для работы; контейнеры — просто удобная обёртка для локальной разработки.
Разовая настройка: указываем document root на web/
Заходим в hPanel, открываем Hosting, находим нужный домен и ищем настройки “Document root” или “Website directory”. Меняем с public_html на путь, где находится директория web/ внутри корня домена. На Hostinger путь будет выглядеть примерно так:
/home/u336386691/domains/yourdomain.com/webЭто разовый шаг, который большинство туториалов полностью игнорирует. Если его пропустить, посетители попадают в корень репозитория и видят пустую страницу или листинг директорий. Все последующие шаги деплоя зависят от правильной настройки этого.
Первый деплой
- Подключаемся по SSH:
ssh your-user@your-host -p 65002(Hostinger использует нестандартный SSH-порт). - Переходим в корень домена и клонируем репозиторий:
cd /home/u336386691/domains/yourdomain.com git clone [email protected]:youruser/yourrepo.git . - Запускаем Composer для установки WordPress core и плагинов:
composer install --no-dev --optimize-autoloaderЕсли Composer не в
$PATH, скачиваем его на месте:php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php composer-setup.php php composer.phar install --no-dev --optimize-autoloader - Создаём продакшн-
.envвручную:nano .envВставляем продакшн-значения. Имя базы данных и пользователь будут содержать префикс cPanel-аккаунта. Точные хост и учётные данные смотрим в hPanel в разделе Databases.
- Проверяем, что сайт открывается по домену. Если видим пустую страницу, значит document root указывает не на
web/, а на корень репозитория.
Последующие деплои
Каждый следующий деплой — одна и та же последовательность. Давно выучил наизусть:
- Пушим изменения в
origin/main. - Подключаемся по SSH.
- Пуллим:
git pull origin main - Запускаем Composer только если изменился
composer.lock:composer install --no-dev --optimize-autoloader - Сбрасываем кэши:
wp cache flush && wp litespeed-purge all - Если меняли слаги, rewrite rules или структуру permalinks:
wp rewrite flush --hard
Это весь деплой. При быстром соединении занимает меньше двух минут. Флаг --no-dev важен: он пропускает пакеты вроде kint, whoops и тестовых инструментов, которым не место на живом сервере.
Что DISALLOW_FILE_MODS делает с рабочим процессом
Поставьте DISALLOW_FILE_MODS=true в продакшн-.env, и установщики плагинов и тем в wp-admin исчезнут. WordPress не позволит никому устанавливать, обновлять или удалять плагины и темы через браузер. Точнее: DISALLOW_FILE_MODS блокирует любую запись в файловую систему из браузера. Автоматические обновления core — отдельный параметр (AUTOMATIC_UPDATER_DISABLED), но на практике оба выставлены в true на Bedrock-продакшене, потому что все изменения всё равно идут через Composer и git.
Это сделано намеренно, и для Bedrock-проекта это правильное решение. Все изменения проходят через Composer и git. Получаем чистую историю изменений, воспроизводимые деплои и никакого расхождения конфигураций от нажатия кнопки “обновить” в wp-admin в пятницу вечером.
Практическое изменение рабочего процесса: добавление плагина означает редактирование composer.json, коммит и деплой. Для плагина из реестра WordPress Packagist:
composer require wpackagist-plugin/redirectionКоммитим обновлённые composer.json и composer.lock, пушим, делаем pull на сервере, запускаем composer install --no-dev, активируем плагин через WP-CLI:
wp plugin activate redirectionАктивация плагинов по-прежнему работает — WordPress может активировать уже установленный плагин, просто не может скачивать или записывать новые файлы. Одно, что стоит задокументировать для любого коллаборатора или клиента: если они пытаются установить плагин из wp-admin и ничего не происходит — это не сломано. Это заблокировано намеренно.
Не ставьте DISALLOW_FILE_MODS=true локально. Ваше окружение разработки (задаётся через WP_ENV=development, загружающий config/environments/development.php) должно оставлять модификацию файлов доступной, чтобы можно было свободно тестировать плагины перед добавлением в Composer.
Почему можно обойтись без Trellis
Trellis — это Ansible-инструмент для провижнинга серверов от Roots. Документация Roots преподносит его как естественного компаньона к Bedrock, но он опционален и плохо подходит для shared-хостинга.
Честный trade-off:
| Trellis + Bedrock | Docker локально + bare PHP (эта статья) | |
|---|---|---|
| Провижнинг сервера | Автоматизирован через Ansible playbooks | Ручная, разовая настройка hPanel |
| Zero-downtime деплои | Встроены (symlink swap) | Не доступны; git pull сразу идёт в прод |
| SSL | Автоматически через Let’s Encrypt | Через hPanel Hostinger (один клик) |
| Требуемая инфраструктура | VPS или выделенный сервер | Любой shared-хост с SSH и Composer |
| Порог входа | Ansible, Vagrant или Multipass, конфиг Trellis | Основы Docker Compose, SSH |
| Подходит для | 3+ разработчиков, клиентские сайты с откатами | Соло-разработка, небольшие команды, личные проекты |
Выбирайте Trellis, если у вас VPS, нужны автоматические откаты или несколько клиентских сайтов на одном сервере. Обходитесь без него, если работаете на shared-хостинге, в одиночку, и деплой через git pull достаточно быстрый. Для личного портфолио или небольшого продакшн-сайта накладные расходы Trellis реальны, а выгода — минимальна.
Отладка самых частых проблем
“Error establishing a database connection” локально
Проверьте DB_HOST в локальном .env. Если там localhost или 127.0.0.1 — замените на db, имя сервиса Compose. Именно по имени контейнеры в одной Compose-сети находят друг друга. Ничто другое внутри контейнера не резолвится.
Composer на сервере падает с “command not found”
Hostinger не всегда добавляет Composer в $PATH по умолчанию. Сначала запустите which composer. Если ничего не вернул — скачайте инсталлятор напрямую и используйте php composer.phar в этой сессии, или добавьте бинарник Composer в пользовательский path в ~/.bashrc.
WordPress загружается, но плагины отсутствуют
Сделали pull, но не запустили composer install. Плагины — не файлы в репозитории, а Composer-пакеты. Если composer.lock изменился при pull, установленные пакеты устарели до тех пор, пока не запустите composer install --no-dev.
В wp-admin нет установщика плагинов
В продакшн-.env выставлено DISALLOW_FILE_MODS=true. Это ожидаемое поведение. Устанавливайте плагины через Composer, активируйте через WP-CLI.
Загрузки пропали после деплоя
web/app/uploads/ добавлен в gitignore. Загруженные пользователями файлы живут только на файловой системе сервера — их нет в репозитории. Для переноса загрузок между окружениями используйте SFTP или плагин вроде WP Offload Media. Это фундаментальное ограничение git-based деплоев, не специфика Bedrock.
Продакшн-.env случайно попал в коммит
Немедленно добавьте .env в .gitignore, если его там ещё нет (стандартный .gitignore Bedrock его уже включает). Смените все секреты в файле: пароль базы данных, WordPress salts, любые API-ключи. Закоммиченный .env с реальными данными нужно считать полностью скомпрометированным, даже если репозиторий приватный. Приватные репозитории утекают.
Часто задаваемые вопросы
- Нужен ли Trellis, чтобы использовать Bedrock на продакшене?
- Нет. Trellis — опциональный Ansible-инструмент провижнинга от той же команды. Bedrock — обычный PHP, который работает на любом сервере с PHP 8.x, MySQL и настроенным document root. Shared-хостинга с SSH-доступом достаточно.
- Почему DB_HOST должен быть “db”, а не “localhost” в Docker Compose?
- Docker Compose создаёт приватную сеть между сервисами. Сервисы находят друг друга по имени, а не по IP. MySQL-контейнер называется
dbв Compose-файле — это и есть hostname.localhostвнутри PHP-контейнера указывает на сам PHP-контейнер, в котором нет никакого MySQL-процесса. - Можно ли запускать Composer на Hostinger shared-хостинге?
- Да, на тарифе Business и выше, который включает SSH-доступ. После подключения выполните
which composer. Если Composer не в$PATH, скачайте инсталлятор командойphp -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"и вызывайте его какphp composer.phar. - Что DISALLOW_FILE_MODS=true ломает в WordPress?
- Убирает установщики плагинов и тем из wp-admin и блокирует любую запись в файловую систему из браузера. Активация плагинов по-прежнему работает — WordPress может активировать уже установленный плагин, просто не может скачивать или записывать новые файлы. Всё идёт через Composer и git.
- Как синхронизировать загрузки WordPress между локальным окружением и продакшеном?
web/app/uploads/добавлен в gitignore. Чтобы работать с продакшн-медиа локально, стяните директорию uploads через SFTP или синхронизируйте с помощьюrsync. Для серьёзного продакшн-сайта WP Offload Media переносит загрузки в S3 или совместимое объектное хранилище, доступное из обоих окружений.- Работает ли эта настройка с Polylang и другими mu-плагинами?
- Да. Плагины, требующие загрузки через mu-plugins, работают точно так же: добавьте loader-файл в
web/app/mu-plugins/и закоммитьте его. Например, Polylang Pro лежит вweb/app/mu-plugins/polylang-pro/рядом сpolylang-pro-loader.php. Оба файла в репозитории; никакой специальной Docker-конфигурации не нужно.
Связанные статьи
- CSS работает локально, но ломается на продакшене — разбирает проблему LiteSpeed UCSS, которая проявляется уже после того, как Bedrock заработал на Hostinger. Следующая острая грань после этой.
- AI-агенты в процессе разработки — о безопасном цикле деплоя и верификации, который я использую после каждого
git pullна этом стеке.




