Олексій Синяєв
Навігація сторінкою статті
Статті 6 хв читання March 21, 2026

Docker і WordPress для локальної розробки

Коротко Реальний виграш Docker для WordPress — паритет і онбординг: одна й та сама версія PHP, розширення та MySQL у всіх, запуск однією командою замість дня налаштування. Пастки специфічні для WordPress, а не для…

Зміст

Коротко

  • Реальний виграш Docker для WordPress — паритет і онбординг: одна й та сама версія PHP, розширення та MySQL у всіх, запуск однією командою замість дня налаштування.
  • Пастки специфічні для WordPress, а не для Docker: DB_HOST — це ім’я сервісу, а не localhost; WordPress зберігає абсолютні URL у базі; а UID користувача контейнера воює з правами на файли.
  • Монтуйте свій код через bind-mount; ніколи не монтуйте vendor/, wp/ чи node_modules; тримайте базу в іменованому томі, щоб вона пережила docker compose down.
  • Це про локальну розробку. Про продакшен-деплой через git pull на шаред-хостингу — у гайді з Bedrock із посиланням у кінці.

Баг «у мене ж працює», який остаточно підштовхнув мене до Docker для WordPress, був розширенням PHP. Плагіну потрібен був intl, на моєму Mac він був, у колеги — ні, і ми втратили півдня на білий екран, який відтворювався лише в одного з нас. Це клас проблем, який прибирають контейнери: не магією, а тим, що роблять рантайм файлом у репозиторії, а не властивістю чийогось ноутбука.

Ця стаття — про локальну половину запуску WordPress у Docker: паритет, робочий процес і WordPress-специфічні пастки, які загальні Docker-туторіали пропускають. Продакшен-деплой на шаред-хостингу — інша задача зі своїми гострими кутами, і в неї свій гайд у кінці.

Що Docker реально лагодить у розробці WordPress

WordPress незвично схильний до дрейфу оточення. Він працює на версії PHP, наборі розширень, версії MySQL і вебсервері, і плагін може тихо залежати від будь-якого з них. Контейнери фіксують усі чотири у версіонованому конфігу, тож стек однаковий у всіх і відтворюваний у CI.

Конкретні вигоди, в порядку того, наскільки вони важливі день у день:

Онбординг

Новий розробник запускає одну команду й отримує весь стек замість README, повного brew install.

Паритет версій

Зафіксуйте PHP і MySQL під продакшен, щоб «працює локально» щось значило.

Одноразовий стан

Зламали базу — видалили том, переімпортували. Без страху зіпсувати локальну установку.

Тест плагінів

Підніміть одноразовий стек, щоб протестувати плагін чи апгрейд PHP, не чіпаючи робочу установку.

Стек розробки, яким реально можна користуватися

Демо з одним контейнером, яке вам показують усі, годиться для п’ятихвилинного погляду й марне для справжньої роботи:

Bash
docker run --name wp -p 80:80 -d wordpress:latest

У нього немає постійної бази, немає доступу до коду теми й плагінів і немає зручного способу запускати WP-CLI. Справжній розробці потрібен багатосервісний стек із примонтованим кодом і збереженою базою:

Code
services:
  app:
    image: wordpress:php8.3-apache
    ports:
      - "8880:80"
    volumes:
      - ./wp-content:/var/www/html/wp-content
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
    depends_on:
      - db

  db:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
      MYSQL_ROOT_PASSWORD: root
    volumes:
      - db_data:/var/lib/mysql

volumes:
  db_data:

Два рішення тут важливіші, ніж здаються. Версія PHP зафіксована в тегу образу під продакшен — дрейф між локальним PHP 8.3 і продакшеном 8.1 — це саме той баг, який Docker має запобігати, тож не лишайте його теліпатися на latest. А база живе в іменованому томі db_data, який переживає docker compose down; код — це bind-mount, тож правки в редакторі видно одразу.

WordPress-специфічні пастки

Це проблеми, яких немає в загальному Docker-туторіалі, бо це проблеми WordPress, які просто спливають усередині Docker.

DB_HOST — це ім’я сервісу, а не localhost

Усередині мережі Compose контейнери знаходять одне одного за іменем сервісу. Хост бази — db, а не localhost і не 127.0.0.1. Якщо виставити localhost, WordPress намагається звернутися до сокета, якого немає в контейнері застосунку, і ви отримуєте «Error establishing a database connection» без подробиць.

WordPress зберігає абсолютні URL у базі. Опції siteurl і home, а також URL, зашиті в контент постів, — це повні рядки http://localhost:8880. Імпортуйте дамп продакшен-бази локально — і кожне посилання, картинка й редирект досі вказують на продакшен. Лагодьте це безпечним до серіалізації search-replace, а не сирим SQL UPDATE, який псує серіалізовані дані:

Bash
docker compose exec app wp search-replace 'https://example.com' 'http://localhost:8880' --all-tables --skip-columns=guid

Володіння файлами воює з вами. Образ Apache працює під www-data (UID 33). Файли, які він пише — завантаження, згенерований CSS, установки плагінів — опиняються у володінні UID 33 на вашому хості, а файли, які ви створюєте під своїм користувачем, можуть бути недоступні для запису контейнеру. На Linux особливо це проявляється як збої завантаження. Чисте рішення — вирівняти користувача контейнера з вашим хостовим UID або тримати записувані директорії (uploads) на томі, яким володіє контейнер, лишивши код звичайним bind-mount.

Запускайте WP-CLI через контейнер. WP-CLI потрібні ті самі PHP і база, що в сайту, тож запускайте його всередині контейнера застосунку, а не на хості:

Bash
docker compose exec app wp plugin list
docker compose exec app wp db export backup.sql

Що не монтувати

Bind-mount — це те, як ваш код потрапляє в контейнер, але монтування не тих директорій — найчастіший спосіб уповільнити стек чи зламати його вщент.

Монтування vendor/ або wp/

Директорії під управлінням Composer мають жити в образі, а не перекриватися хостовими файлами. Їх монтування кличе розбіжність версій.

Монтування node_modules

Архітектури хоста й контейнера різняться. Збирайте ассети всередині контейнера або тримайте node_modules поза монтуванням.

Плаваюча версія PHP

Використання wordpress:latest замість зафіксованого PHP-тега повертає саме той дрейф, який Docker має прибирати.

База в bind-mount

Дані MySQL на хостовому bind-mount повільні й схильні до проблем з правами. Використовуйте іменований том.

Коміт завантажень

wp-content/uploads — це користувацькі дані, а не код. Тримайте їх поза git і поза образом.

localhost як хост бази

Контейнери дістають базу за іменем сервісу. localhost вказує контейнер застосунку на самого себе.

Опціональні сервіси, які варто додати

Коли базовий стек працює, кілька додаткових сервісів помітно покращують локальну розробку. Пастка пошти на кшталт Mailpit перехоплює вихідні листи, тож скидання пароля й сповіщення потрапляють у вебінтерфейс, а не в порожнечу. Redis дає справжній об’єктний кеш, що збігається з поведінкою продакшену. Окремий контейнер-воркер черги дозволяє ганяти джоби так, як вони реально виконуються. Кожен — це кілька рядків у тому ж Compose-файлі, версіонованих разом з рештою.

FAQ

Чому WordPress показує «Error establishing a database connection» у Docker?
Майже завжди справа в хості бази. Усередині Docker Compose сервіси дістають одне одного за іменем сервісу, тож DB_HOST має бути іменем сервісу бази (db), а не localhost. Вторинна причина — контейнер застосунку стартує до готовності MySQL; depends_on плюс повтор підключення це розв’язують.
Чому URL картинок неправильні після імпорту продакшен-бази?
WordPress зберігає абсолютні URL у базі. Запустіть безпечний до серіалізації wp search-replace з продакшен-домену на ваш локальний URL по всіх таблицях, пропустивши стовпець guid. Ніколи не робіть це сирим SQL UPDATE — він псує серіалізовані дані опцій.
Монтувати vendor/ і wp/ як томи?
Ні. Директорії під управлінням Composer належать образу, щоб версії лишалися узгодженими. Монтуйте через bind-mount лише свій код (теми, плагіни, mu-plugins). Монтування керованих директорій — частий джерело розбіжностей «у CI працює, локально ні».
Як запускати WP-CLI на Dockerized WordPress-сайті?
Запускайте його всередині контейнера застосунку через docker compose exec app wp <command>, щоб він використовував ту саму версію PHP і підключення до бази, що й сайт. Обгортка Makefile навколо цієї команди економить багато друкування.

Схожі статті

Оновлено: June 17, 2026

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

LinkedIn X Email

Зв'язатися

Працюєте над схожою задачею? Давайте обговоримо.

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

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

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

Статті

June 21, 2026

Як зрозуміти, що AI-агент втратив контекст: state-canary в AGENTS.md і CLAUDE.md

State-canary — простий observability-патерн для AI coding agents: один рядок стану в кожній відповіді…
Статті

June 14, 2026

Частина 3. Місяць з AI-щоденником: як шукати зв’язки між сном, стресом і тренуваннями

Як аналізувати AI-щоденник після першого місяця: виправлення розпізнавання, чесна рефлексія з джерелами, Obsidian, вартість…
Статті

June 14, 2026

Частина 2. Hermes Agent + DeepSeek на Ubuntu: повний мануал AI-щоденника в Telegram

Покроковий мануал: Hermes Agent і DeepSeek на Ubuntu, закритий Telegram-бот, локальний faster-whisper, Markdown vault,…