Традиционная локальная веб-разработка на нативных пакетах (MAMP, XAMPP, OpenServer, локальные бинарники Homebrew/APT) неизбежно порождает классическую проблему: «На моем компьютере все работает, а на боевом сервере падает» (Environment Drifting).
В реальной практике инженерные команды сталкиваются со следующими проблемами локальных монолитных установок:
imagick маскирует ошибки продакшена, где используется PHP 8.3 с жестко урезанным набором модулей или устаревшим cURL.wp_mail() отправляет реальные почтовые уведомления живым покупателям из продакшн-дампа базы данных.http://localhost ломает тестирование современных браузерных API (Web Crypto, Service Workers, Geolocation, Payment Request API), а также вызывает ошибки смешанного контента (Mixed Content) при обращении к внешним сервисам.www-data (UID 33), блокируя редактирование файлов хост-пользователем (UID 1000) без хаотичных chmod -R 777.Промышленный стандарт решения — декларативная контейнеризация стека через Docker Compose с изоляцией сервисов, виртуальным почтовым сервером Mailpit, проксированием трафика через Traefik и пошаговой отладкой через Xdebug 3.
Профессиональное окружение WordPress состоит не из одного тяжелого контейнера-монолита, а из дискретного пула узкоспециализированных микросервисов:
*.agenticlab.local и локальный TLS-сертификат.Каждый сервис в манифесте Compose выполняет строго одну функцию и изолирован во внутренней виртуальной сети Docker. Связь между Nginx и PHP происходит через сокет или сетевой порт 9000 по протоколу FastCGI, а запросы к базе данных маршрутизируются по внутреннему DNS-имени контейнера (db).
Описываются сервисы (app, nginx, db, mailpit, traefik, cli), точки монтирования (Volumes) для темы/плагинов и общая сеть agenticlab-network.
Файлы темы монтируются в режиме Bind Mount, а данные MySQL — строго в Named Volume для высокой скорости I/O.
Многоэтапный Dockerfile компилирует расширения gd, pdo_mysql, mysqli, zip, intl, redis и конфигурирует Xdebug 3 для удаленной отладки в IDE.
UID и GID пользователя www-data внутри контейнера синхронизируются с хост-пользователем (1000:1000).
Nginx берет на себя отдачу статики (CSS, JS, изображения), блокирует доступ к скрытым файлам (.git, .env) и передает PHP-запросы на порт 9000 контейнера app.
Поддерживается перенаправление всех запросов на index.php для ЧПУ WordPress.
Traefik перехватывает запросы на стандартные порты 80/443 хоста, автоматически подтягивает локальные SSL-сертификаты и проксирует трафик на домен https://agenticlab.local.
Устраняется необходимость указывать нестандартные порты (вроде :8080) в браузере.
Автоматизированный скрипт безопасно дампит боевую БД по SSH, импортирует в локальный MariaDB, выполняет wp search-replace и скачивает uploads через rsync.
Скрипт защищен блокировкой от случайного запуска в сторону продакшена (Push Protection).
Запрос к https://agenticlab.local на порт 443 хоста перехватывается Traefik Reverse Proxy
Traefik валидирует локальный доверенный сертификат mkcert и перенаправляет HTTP-трафик на Nginx
Отдача статики из /wp-content напрямую; проксирование PHP-запросов на app:9000 (FastCGI)
PHP 8.3 обрабатывает код темы/плагинов, подключен к СУБД db:3306 и Xdebug в IDE на хосте
Все вызовы wp_mail() направляются на mailpit:1025. Ни одно письмо не покидает локальную сеть
Выполнение скриптов миграции, очистки кэша и search-replace без установки PHP на хосте
Почему монтирование всей корневой папки WordPress (включая ядро и базу данных) через классический Bind Mount на хост-машину Windows (без WSL2) или macOS приводит к катастрофическому падению производительности (открытие страницы занимает более 5–8 секунд)?
При отладке PHP-кода в Docker-контейнере через IDE (VS Code или PhpStorm) на хост-машине Xdebug не останавливается на точках останова (Breakpoints). Какая настройка в xdebug.ini отвечает за установление обратного сетевого соединения от контейнера к IDE на хосте?
Ниже представлены четыре готовых инфраструктурных артефакта, образующих профессиональный локальный стек.
Манифест спецификации Compose v2 описывает дискретные сервисы: веб-сервер, кастомный PHP-FPM с монтированием исходников, СУБД MariaDB 11, почтовую ловушку Mailpit, прокси Traefik v3 и служебный WP-CLI.
# Спецификация Docker Compose v2 для WordPress Enterprise разработки
name: agenticlab-local
services:
# 1. Reverse Proxy: Traefik v3 для автоматического HTTPS и локальных доменов
traefik:
image: traefik:v3.1
container_name: agenticlab_traefik
restart: unless-stopped
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
ports:
- "80:80"
- "443:443"
- "8080:8080" # Дашборд Traefik
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./docker/traefik/certs:/etc/certs:ro
- ./docker/traefik/dynamic.yaml:/etc/traefik/dynamic.yaml:ro
networks:
- agenticlab-network
# 2. Веб-сервер Nginx для статики и FastCGI шлюза
nginx:
image: nginx:1.27-alpine
container_name: agenticlab_nginx
restart: unless-stopped
depends_on:
app:
condition: service_started
volumes:
- wordpress_core:/var/www/html:ro
- ./wp-content:/var/www/html/wp-content
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
networks:
- agenticlab-network
labels:
- "traefik.enable=true"
- "traefik.http.routers.agenticlab.rule=Host(`agenticlab.local`)"
- "traefik.http.routers.agenticlab.entrypoints=websecure"
- "traefik.http.routers.agenticlab.tls=true"
# 3. PHP 8.3-FPM с расширениями и Xdebug 3
app:
build:
context: .
dockerfile: docker/php/Dockerfile
args:
PHP_VERSION: "8.3"
UID: 1000
GID: 1000
container_name: agenticlab_app
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: agenticlab_user
WORDPRESS_DB_PASSWORD: agenticlab_password
WORDPRESS_DB_NAME: agenticlab_db
volumes:
- wordpress_core:/var/www/html
- ./wp-content:/var/www/html/wp-content
- ./docker/php/xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini:ro
- ./docker/php/custom.ini:/usr/local/etc/php/conf.d/custom.ini:ro
networks:
- agenticlab-network
# 4. База данных MariaDB 11.4 LTS с Healthcheck
db:
image: mariadb:11.4
container_name: agenticlab_db
restart: unless-stopped
environment:
MYSQL_DATABASE: agenticlab_db
MYSQL_USER: agenticlab_user
MYSQL_PASSWORD: agenticlab_password
MYSQL_ROOT_PASSWORD: root_secret_password
volumes:
- db_data:/var/lib/mysql
networks:
- agenticlab-network
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s
timeout: 5s
retries: 10
# 5. Mailpit: SMTP-ловушка исходящей почты
mailpit:
image: axllent/mailpit:latest
container_name: agenticlab_mailpit
restart: unless-stopped
networks:
- agenticlab-network
labels:
- "traefik.enable=true"
- "traefik.http.routers.mailpit.rule=Host(`mail.agenticlab.local`)"
- "traefik.http.routers.mailpit.entrypoints=websecure"
- "traefik.http.routers.mailpit.tls=true"
- "traefik.http.services.mailpit.loadbalancer.server.port=8025"
# 6. WP-CLI для сервисных скриптов и автоматизации
cli:
image: wordpress:cli-php8.3
container_name: agenticlab_cli
user: "1000:1000"
depends_on:
- db
- app
volumes:
- wordpress_core:/var/www/html
- ./wp-content:/var/www/html/wp-content
networks:
- agenticlab-network
entrypoint: ["wp"]
volumes:
wordpress_core: # Именованный том для ядра WP (быстрый I/O на Linux)
db_data: # Именованный том для файлов СУБД
networks:
agenticlab-network:
driver: bridge
Dockerfile компилирует все обязательные расширения ядра WordPress, почтовый клиент msmtp для маршрутизации на Mailpit и Xdebug 3.
# Базовый образ официального PHP 8.3 FPM
FROM php:8.3-fpm-alpine
ARG UID=1000
ARG GID=1000
# Установка системных зависимостей для сборки расширений
RUN apk add --no-cache \
bash \
curl \
freetype-dev \
libjpeg-turbo-dev \
libpng-dev \
libzip-dev \
icu-dev \
msmtp \
linux-headers \
$PHPIZE_DEPS
# Конфигурация и компиляция PHP-расширений
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
bcmath \
exif \
gd \
intl \
mysqli \
opcache \
pdo_mysql \
zip
# Установка расширений через PECL: Redis и Xdebug 3
RUN pecl install redis-6.0.2 xdebug-3.3.2 \
&& docker-php-ext-enable redis xdebug
# Очистка временных файлов сборщика
RUN apk del $PHPIZE_DEPS \
&& rm -rf /tmp/pear
# Настройка msmtp для перенаправления всей почты на сервис mailpit
RUN printf "account default\nhost mailpit\nport 1025\nauto_from on\n" > /etc/msmtprc \
&& printf "sendmail_path = /usr/bin/msmtp -t\n" > /usr/local/etc/php/conf.d/mail.ini
# Синхронизация UID/GID www-data с пользователем хост-машины
RUN apk add --no-cache shadow \
&& usermod -u ${UID} www-data \
&& groupmod -g ${GID} www-data
WORKDIR /var/www/html
USER www-data
Конфигурационный файл Xdebug 3 для удаленной отладки:
[xdebug]
; Режим отладки и трассировки
xdebug.mode = debug,develop
xdebug.start_with_request = yes
; Сетевой адрес хост-машины (авто-резолв через Docker)
xdebug.client_host = host.docker.internal
xdebug.client_port = 9003
; Журнал отладки для локализации проблем связи с IDE
xdebug.log = /tmp/xdebug.log
xdebug.log_level = 0
xdebug.discover_client_host = false
xdebug.idekey = PHPSTORM
Конфигурация с изоляцией скрытых директорий, кэшированием статических ресурсов и прозрачным роутингом ЧПУ.
server {
listen 80;
server_name agenticlab.local;
root /var/www/html;
index index.php;
client_max_body_size 64M;
# Блокировка доступа к скрытым системным файлам (.git, .env, .htaccess)
location ~ /\.(?!well-known).* {
deny all;
access_log off;
log_not_found off;
}
# Блокировка прямого исполнения PHP в каталоге загрузок
location ~* /wp-content/uploads/.*\.php$ {
deny all;
}
# Кэширование статических ассетов
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|webp|avif)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
access_log off;
try_files $uri =404;
}
# Маршрутизация ЧПУ WordPress
location / {
try_files $uri $uri/ /index.php?$args;
}
# Проксирование PHP-запросов на контейнер app:9000
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass app:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_read_timeout 300;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
}
}
Промышленный bash-скрипт с защитой от запуска в сторону продакшена, десериализацией строк через wp search-replace и инкрементальной передачей медиатеки через rsync.
#!/usr/bin/env bash
# ==============================================================================
# sync-environment.sh — Безопасная синхронизация продакшн-данных в Docker dev-окружение
# ==============================================================================
set -euo pipefail
# Конфигурационные параметры удаленного сервера
REMOTE_SSH="[email protected]"
REMOTE_WP_PATH="/var/www/agenticlab"
REMOTE_URL="https://agenticlab.app"
# Локальные параметры окружения
LOCAL_URL="https://agenticlab.local"
DUMP_FILE="/tmp/agenticlab_prod_dump.sql"
echo "🔒 [Безопасность] Проверка рабочего контекста..."
if [[ "${1:-pull}" == "push" ]]; then
echo "❌ ОШИБКА: Прямой PUSH локальной базы на боевой сервер запрещен политикой безопасности!"
echo "Миграции структуры базы данных на продакшен выполняются только через CI/CD релизы."
exit 1
fi
echo "🚀 [1/5] Создание дампа базы данных на боевом сервере..."
ssh "${REMOTE_SSH}" "wp db export --path=${REMOTE_WP_PATH} --single-transaction --quick ${DUMP_FILE}"
echo "📥 [2/5] Скачивание дампа базы на локальную машину..."
scp "${REMOTE_SSH}:${DUMP_FILE}" "./dump.sql"
ssh "${REMOTE_SSH}" "rm -f ${DUMP_FILE}"
echo "💾 [3/5] Импорт базы данных в локальный MariaDB контейнер..."
docker compose exec -T db mysql -u agenticlab_user -pagenticlab_password agenticlab_db < ./dump.sql
rm -f ./dump.sql
echo "🔄 [4/5] Десериализованная замена URL через WP-CLI (search-replace)..."
docker compose run --rm cli search-replace "${REMOTE_URL}" "${LOCAL_URL}" \
--all-tables \
--precise \
--skip-columns=guid \
--report-changed-only
# Отключение боевых сервисов интеграций в локальной базе
echo "🛠️ Перевод плагинов в режим разработки..."
docker compose run --rm cli option update blog_public 0
docker compose run --rm cli config set WP_ENVIRONMENT_TYPE 'local' --type=constant
echo "🖼️ [5/5] Инкрементальная синхронизация медиафайлов (/uploads) через rsync..."
rsync -avz --progress --delete \
--exclude="*.tmp" \
--exclude="cache/" \
"${REMOTE_SSH}:${REMOTE_WP_PATH}/wp-content/uploads/" \
"./wp-content/uploads/"
echo "✅ Синхронизация успешно завершена! Окружение доступно: ${LOCAL_URL}"
При работе с Docker-окружениями WordPress часто допускаются следующие критические ошибки:
--precise в wp search-replace#s:18:"https://remote.com"). Если заменить строку обычным SQL-запросом REPLACE() или без флага --precise, длина строки (s:18) станет неверной, и PHP unserialize() вернет false.wp search-replace --precise --all-tables.msmtp на уровне образа PHP и сброс опций почтовых плагинов при импорте.wp-includes и wp-admin через двусторонний Bind Mount на хостовую файловую систему.wordpress_core), а через Bind Mount связывать только рабочую директорию проекта (./wp-content/themes/my-theme).