Алексей Синяев
RUUKESEN
Навигация по странице статьи

Статья блога Статьи 5 мин чтения

WordPress Bedrock и Docker: локальная разработка и деплой на shared-хостинг

Как запустить WordPress Bedrock с Docker Compose локально и задеплоить на Hostinger без Trellis. Проблема двух .env-файлов, шаги деплоя на bare PHP и все подводные камни.

Terminal con docker compose up ejecutándose en un proyecto WordPress Bedrock, junto a los archivos docker-compose.yml y .env abiertos
Содержание

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 реально делает с рабочим процессом. Остальное — для полноты картины, но именно эти три раздела — причина существования статьи.

Два отдельных окружения WordPress Bedrock: локальный Docker Compose с app и database контейнерами и production shared hosting с PHP и MySQL, разделённые предупреждающим маркером

Что 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 -d

WP-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_HOSTdb (имя сервиса Compose)127.0.0.1 или внутренний хост cPanel
DB_NAMEwordpressu336386_prod (с префиксом cPanel)
DB_USERwordpressu336386_wp
WP_HOMEhttp://localhost:8880https://yourdomain.com
WP_SITEURLhttp://localhost:8880/wphttps://yourdomain.com/wp
WP_ENVdevelopmentproduction
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

Это разовый шаг, который большинство туториалов полностью игнорирует. Если его пропустить, посетители попадают в корень репозитория и видят пустую страницу или листинг директорий. Все последующие шаги деплоя зависят от правильной настройки этого.

Первый деплой

  1. Подключаемся по SSH: ssh your-user@your-host -p 65002 (Hostinger использует нестандартный SSH-порт).
  2. Переходим в корень домена и клонируем репозиторий:
    cd /home/u336386691/domains/yourdomain.com
    git clone [email protected]:youruser/yourrepo.git .
  3. Запускаем 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
  4. Создаём продакшн-.env вручную:
    nano .env

    Вставляем продакшн-значения. Имя базы данных и пользователь будут содержать префикс cPanel-аккаунта. Точные хост и учётные данные смотрим в hPanel в разделе Databases.

  5. Проверяем, что сайт открывается по домену. Если видим пустую страницу, значит document root указывает не на web/, а на корень репозитория.

Последующие деплои

Каждый следующий деплой — одна и та же последовательность. Давно выучил наизусть:

  1. Пушим изменения в origin/main.
  2. Подключаемся по SSH.
  3. Пуллим:
    git pull origin main
  4. Запускаем Composer только если изменился composer.lock:
    composer install --no-dev --optimize-autoloader
  5. Сбрасываем кэши:
    wp cache flush && wp litespeed-purge all
  6. Если меняли слаги, 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 + BedrockDocker локально + 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-конфигурации не нужно.

Связанные статьи

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

LinkedIn X Email

Связаться

Если статья пересекается с вашей задачей, можете написать мне.

Открыт к обсуждению архитектуры, Laravel, WordPress, производительности и практических инженерных задач.

Связаться Смотреть кейсы

Смотрите также

May 30, 2026

Настройка Claude Code: руководство для начинающих

Пошаговое руководство по установке Claude Code, настройке CLAUDE.md, модели разрешений и первым реальным задачам.…

May 29, 2026

CSS работает локально, но ломается на проде: как LiteSpeed UCSS вырезает ваши стили

Реальная история дебага: тема работала локально, но сломалась на проде, потому что LiteSpeed UCSS…

May 20, 2026

Claude Code субагенты: готовые агенты для более безопасного и дешевого workflow

Практический гайд 2026 по субагентам Claude Code: готовые .claude/agents примеры для explorer, planner, reviewer,…