Коротко
- Реальный выигрыш 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, не трогая рабочую установку.
Стек разработки, которым реально можно пользоваться
Демо с одним контейнером, которое вам показывают все, годится для пятиминутного взгляда и бесполезно для настоящей работы:
docker run --name wp -p 80:80 -d wordpress:latestУ него нет постоянной базы, нет доступа к коду темы и плагинов и нет удобного способа запускать WP-CLI. Настоящей разработке нужен многосервисный стек с примонтированным кодом и сохраняемой базой:
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, который портит сериализованные данные:
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 и база, что у сайта, так что запускайте его внутри контейнера приложения, а не на хосте:
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. Никогда не делайте это сырым SQLUPDATE— он портит сериализованные данные опций. - Монтировать ли vendor/ и wp/ как тома?
- Нет. Директории под управлением Composer принадлежат образу, чтобы версии оставались согласованными. Монтируйте через bind-mount только свой код (темы, плагины, mu-plugins). Монтирование управляемых директорий — частый источник расхождений «в CI работает, локально нет».
- Как запускать WP-CLI на Dockerized WordPress-сайте?
- Запускайте его внутри контейнера приложения через
docker compose exec app wp <command>, чтобы он использовал ту же версию PHP и подключение к базе, что и сайт. Обёртка Makefile вокруг этой команды экономит много печати.
Похожие статьи
- WordPress Bedrock с Docker локально и чистым PHP в продакшене — про половину с деплоем: релизы через git pull на шаред-хостинг без Trellis и VPS.
- Производительность с Nginx и Apache — про слой веб-сервера, который можно чисто бенчмаркать, когда стек воспроизводим.




