Алексей Синяев
Навигация по странице статьи
Статьи 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 14, 2026

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

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

June 14, 2026

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

Пошаговый мануал: Hermes Agent и DeepSeek на Ubuntu, Telegram-бот с закрытым доступом, локальный faster-whisper,…
Статьи

June 14, 2026

Часть 1. Как я превратил старый игровой ноутбук в AI-дневник самочувствия

Как старый Xiaomi Mi Gaming Laptop стал домашним AI-сервером: Hermes Agent, DeepSeek, Telegram и…