TL;DR
- Bedrock просто реорганізує WordPress з Composer і конфігурацією на основі оточення. Йому не потрібні Trellis, Ansible чи VPS: Docker локально плюс shared hosting чудово працюють.
- Нюанс, якого ніде не описано: ви назавжди тримаєте два окремих файли
.env, боDB_HOST, що працює локально, ніколи не спрацює в продакшні, і навпаки. - Деплой через
git pull(vendor/,web/wp/,uploads/і.envлишаються поза git), document root указує наweb/, і важливо розуміти, щоDISALLOW_FILE_MODSзмінює у вашому процесі в продакшні.
Перший раз, коли я розгортав WordPress Bedrock на shared hosting, я витратив дві години на помилку підключення до бази даних, перш ніж зрозумів причину. Кожен туторіал показував один і той самий файл .env. Кожен зупинявся на docker compose up. Жоден не згадував, що значення DB_HOST, яке потрібне локально, ніколи не спрацює в продакшні, а те, що працює в продакшні, ніколи не спрацює локально. Це два різних файли з двома різними значеннями, якими ви керуєте окремо і назавжди. Нічого складного, якщо знаєш. Просто ніде не написано.
Ця стаття описує саме той Bedrock + Docker setup, на якому працює цей сайт: Docker Compose локально, Hostinger shared hosting у продакшні, деплой через git pull, без Trellis, без Ansible, без VPS. Три розділи, яких немає більше ніде: проблема двох .env, деплой на bare PHP і що насправді робить DISALLOW_FILE_MODS з вашим workflow. Решта — для повноти картини, але саме через ці три розділи стаття й існує.

Що насправді робить Bedrock (і чого не робить)
Bedrock — це WordPress boilerplate від 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 hosting: document root має вказувати на web/, а не на корінь репозиторію. Якщо хостинг бачить корінь репо, відвідувачі отримають список файлів. Про те, як це налаштувати на Hostinger, — в розділі про продакшн.
Що потрібно мати
- Docker Desktop або OrbStack (Mac) — OrbStack швидший і легший на Apple Silicon
- Composer 2.x на хост-машині (тільки для початкового bootstrap; PHP runtime обробляє Docker)
- Git-репозиторій на GitHub або GitLab
- SSH-доступ на shared hosting — 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/ як volume — ними керує 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 socket, якого немає всередині контейнера для MySQL-сервісу. Помилка буде “Error establishing a database connection” без жодних подробиць. Виправлення — одне слово: замінити localhost на db.
Керуйте двома файлами так: додайте в репозиторій .env.example із заглушками і коментарями, що пояснюють, яке значення потрібне в кожному середовищі. Справжні значення зберігайте в менеджері паролів. Ніколи не кладіть їх у репозиторій, 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 hosting не запускає 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.
- Зробіть pull:
git pull origin main - Запускайте Composer тільки якщо змінився
composer.lock:composer install --no-dev --optimize-autoloader - Очистіть кеші:
wp cache flush && wp litespeed-purge all - Якщо змінювали slug, rewrite rules або структуру permalink:
wp rewrite flush --hard
Ось і весь деплой. На швидкому підключенні займає менше двох хвилин. Прапорець --no-dev важливий: він пропускає пакети на кшталт kint, whoops і тестові інструменти, яким не місце на живому сервері.
Що DISALLOW_FILE_MODS робить з вашим workflow
Встановіть DISALLOW_FILE_MODS=true у продакшн .env, і встановлювачі плагінів та тем у wp-admin зникнуть. WordPress не дозволить нікому встановлювати, оновлювати або видаляти плагіни чи теми через браузер. Точніше: DISALLOW_FILE_MODS блокує всі записи в файлову систему з браузера. Автоматичні оновлення core — це окремий перемикач (AUTOMATIC_UPDATER_DISABLED), але на практиці обидва встановлюються в true на продакшн-сайті Bedrock, бо всі зміни однаково йдуть через Composer і git.
Це свідоме рішення, і для Bedrock-setup воно правильне. Кожна зміна проходить через Composer і git. Ви отримуєте чистий audit trail, відтворювані деплої і жодного config drift від того, що хтось натиснув “оновити” у wp-admin в п’ятницю ввечері.
Практична зміна у workflow: щоб додати плагін, потрібно відредагувати 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 — це інструмент Roots на базі Ansible для провізіонування серверів. Документація Roots подає його як природного супутника Bedrock, але він опціональний і не підходить для shared hosting.
Чесний порівняльний аналіз:
| Trellis + Bedrock | Docker локально + bare PHP (ця стаття) | |
|---|---|---|
| Провізіонування сервера | Автоматично через Ansible playbooks | Вручну, одноразове налаштування hPanel |
| Деплой без простою | Вбудовано (symlink swap) | Недоступно; git pull одразу виходить у прод |
| SSL | Автоматично через Let’s Encrypt | Налаштовується в hPanel (один клік) |
| Потрібна інфраструктура | VPS або виділений сервер | Будь-який shared hosting з SSH і Composer |
| Поріг входу | Ansible, Vagrant або Multipass, конфіг Trellis | Основи Docker Compose, SSH |
| Підходить для | 3+ розробники, клієнтські сайти з потребою rollback | Соло-розробник або невелика команда, особисті проєкти |
Вибирайте Trellis, якщо у вас є VPS, потрібні автоматичні rollback або ви обслуговуєте кілька клієнтських сайтів з одного сервера. Відмовляйтеся від нього, якщо ви на shared hosting, працюєте самостійно і деплой через 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. Якщо нічого не повертає, завантажте installer напряму і використовуйте php composer.phar для цієї сесії або додайте Composer до шляху користувача в ~/.bashrc.
WordPress завантажується, але плагіни відсутні
Ви зробили pull, але не запустили composer install. Плагіни — це не файли, які ви комітите, а Composer-пакети. Якщо після pull змінився composer.lock, встановлені пакети застарілі до виконання composer install --no-dev.
В wp-admin немає встановлювача плагінів
У продакшн .env встановлено DISALLOW_FILE_MODS=true. Це очікувана поведінка. Встановлюйте плагіни через Composer, активуйте через WP-CLI.
Uploads відсутні після деплою
web/app/uploads/ є в gitignore. Завантажені користувачами файли живуть тільки в файловій системі сервера — їх немає в репозиторії. Щоб перенести uploads між середовищами, використовуйте SFTP або плагін на кшталт WP Offload Media. Це фундаментальне обмеження git-based деплоїв, а не особливість Bedrock.
Продакшн .env випадково потрапив у коміт
Негайно додайте .env до .gitignore, якщо його там ще немає (стандартний .gitignore Bedrock вже його включає). Змініть кожен секрет у файлі: пароль до бази даних, WordPress salts, будь-які API-ключі. Закомічений .env з живими обліковими даними потрібно вважати повністю скомпрометованим, навіть якщо репозиторій приватний. Приватні репозиторії теж витікають.
FAQ
- Чи потрібен Trellis для використання Bedrock у продакшні?
- Ні. Trellis — опціональний інструмент провізіонування на базі Ansible від тієї ж команди. Bedrock — звичайний PHP, який працює на будь-якому сервері з PHP 8.x, MySQL і налаштованим document root. Достатньо shared hosting з SSH-доступом.
- Чому DB_HOST має бути “db”, а не “localhost” у Docker Compose?
- Docker Compose створює приватну мережу між сервісами. Сервіси знаходять один одного за іменем, а не за IP. MySQL-контейнер у Compose-файлі названий
db, тому це і є hostname.localhostвсередині PHP-контейнера посилається на сам PHP-контейнер, де немає жодного MySQL-процесу. - Чи можна запустити Composer на Hostinger shared hosting?
- Так, на тарифі Business і вище, де є SSH-доступ. Після підключення виконайте
which composer. Якщо Composer не в$PATH, завантажте installer командоюphp -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"і запускайте якphp composer.phar. - Що DISALLOW_FILE_MODS=true ламає у WordPress?
- Прибирає встановлювачі плагінів і тем з wp-admin і блокує всі записи в файлову систему з браузера. Активація плагінів досі працює — WordPress може активувати вже встановлений плагін, але не може завантажити або записати нові файли. Натомість все робиться через Composer і git.
- Як керувати WordPress uploads між локальним середовищем і продакшном?
web/app/uploads/є в gitignore. Щоб працювати з продакшн-медіафайлами локально, стягніть директорію uploads через SFTP абоrsync. Для серйозного продакшн-сайту WP Offload Media переміщує uploads на S3 або сумісне об’єктне сховище, до якого мають доступ обидва середовища.- Чи сумісний цей setup з Polylang або іншими mu-plugins?
- Так. Плагіни, що вимагають завантаження через mu-plugin, працюють точно так само: додайте файл-завантажувач у
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.




