Олексій Синяєв
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 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. Решта — для повноти картини, але саме через ці три розділи стаття й існує.

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

Що насправді робить 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 -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/ як volume — ними керує 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 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

Це одноразовий крок, який більшість туторіалів повністю пропускає. Якщо пропустити його, відвідувачі потрапляють на корінь репозиторію і бачать порожню сторінку або список файлів. Всі наступні кроки деплою залежать від правильного налаштування цього параметра.

Перший деплой

  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. Зробіть pull:
    git pull origin main
  4. Запускайте Composer тільки якщо змінився composer.lock:
    composer install --no-dev --optimize-autoloader
  5. Очистіть кеші:
    wp cache flush && wp litespeed-purge all
  6. Якщо змінювали 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 + BedrockDocker локально + 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 не потрібно.

Пов’язані статті

Поділитися статтею

LinkedIn X Email

Зв'язатися

Якщо стаття перетинається з вашим завданням, можете написати мені.

Відкритий до розмови про архітектуру, Laravel, WordPress, продуктивність і практичні інженерні задачі.

Зв'язатися Переглянути кейси

Дивіться також

May 30, 2026

Налаштування Claude Code: посібник для початківців

Покрокове керівництво зі встановлення Claude Code, налаштування CLAUDE.md, моделі дозволів і перших реальних завдань.…

May 20, 2026

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

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

May 17, 2026

AI-агенти в робочому процесі розробника: практичний гайд

Практичний гайд на травень 2026 про AI-агентів у розробці: Claude Code, Codex, Cursor, Gemini,…