同じサーバー上で、3つの WordPress が別々の DB で動いているサイトがあります。ローカルの開発環境を、Docker Compose 1つでまとめて動かせるようにしました。
本番は手動 FTP で運用している昔ながらの構成で、ディレクトリの入れ子や、WordPress の外にある PHP が本番のパスを前提に書かれている、といった癖があります。本番のコードを書き換えずにローカルで動かすために工夫したところを紹介します。
構成
| サイト | 本番のパス | ローカル | DB |
|---|---|---|---|
| メイン | /(WP 本体は /cms) |
:8080 |
main |
| サブ A | /sub-a(WP 本体は /sub-a/cms) |
:8082 |
sub_a |
| サブ B | /sub-b(WP 本体は /sub-b/cms) |
:8083 |
sub_b |
サービスは次のとおりです。
db:MariaDB 1つ(3サイト共通。DB はサイトごとに分ける)wordpress・sub_a・sub_b:同じイメージから起動phpmyadmin、mailpit(送信メールの確認用)
リポジトリで管理するのは、各サイトのテーマと WordPress の外にある独自の PHP だけです。WordPress のコアはリポジトリに置かず、イメージから入れます。
1. イメージは1つだけビルドして、3サービスで使い回す
3つの WordPress サービスで同じ設定を書かないよう、YAML のアンカーにまとめました。
x-wordpress: &wordpress
image: myproject-wordpress:local
pull_policy: never
depends_on:
db:
condition: service_healthy
services:
wordpress:
<<: *wordpress
build: ./docker # ビルドはこのサービスだけに持たせる
...
sub_a:
<<: *wordpress
...
sub_b:
<<: *wordpress
...
build: を3サービスすべてに書くと、同じイメージを同時にビルドしようとして衝突することがあります。ビルドは1サービスだけに持たせ、残りは同じイメージ名を参照させています。
WordPress のバージョンを固定する
公式の wordpress イメージは、PHP のバージョンを示すタグ(php8.3-apache)を使うと、WordPress のバージョンが勝手に上がります。本番に上げる前の検証に使いたいので、コアは別に指定した版で差し替えています。
FROM wordpress:php8.3-apache
ENV WP_VERSION=7.1.3 \
WP_SHA1=<公式の sha1>
RUN set -eux; \
curl -fsSL -o /tmp/wordpress.tar.gz "https://wordpress.org/wordpress-${WP_VERSION}.tar.gz"; \
echo "${WP_SHA1} */tmp/wordpress.tar.gz" | sha1sum -c -; \
cd /usr/src/wordpress; \
find . -mindepth 1 -maxdepth 1 ! -name wp-content ! -name wp-config-docker.php ! -name .htaccess -exec rm -rf {} +; \
tar -xzf /tmp/wordpress.tar.gz -C /usr/src/wordpress --strip-components=1 --exclude=wordpress/wp-content; \
rm /tmp/wordpress.tar.gz; \
chown -R www-data:www-data /usr/src/wordpress
公式イメージは、初回起動時に /usr/src/wordpress の中身をドキュメントルートへコピーします。その元を差し替えるのがポイントです。あわせて、見積もりシステムが使う pdo_mysql、メール用の msmtp、運用用の wp-cli もこのイメージに入れています。
2. コアは名前付きボリューム、開発するものだけバインドマウント
wordpress:
volumes:
- wp_core:/var/www/html # コア一式(イメージが初回に展開)
- ./docker/wp-config.php:/var/www/html/wp-config.php:ro
- ./docker/.htaccess:/var/www/html/.htaccess:ro
- ./cms/wp-content/themes/main:/var/www/html/wp-content/themes/main
- ./cms/wp-content/plugins:/var/www/html/wp-content/plugins
- ./cms/wp-content/uploads:/var/www/html/wp-content/uploads
- コアは名前付きボリュームに置き、サイトごとに分けます(
wp_core・wp_core_sub_a・wp_core_sub_b)。リポジトリにコアのファイルを置く必要がありません。 - テーマ、プラグイン、メディアだけをリポジトリのディレクトリからマウントするので、テーマの編集はそのままブラウザに反映されます。プラグインとメディアはディスクに置きますが、Git では追跡しません。
3. wp-config.php はローカル専用を1つだけ用意して使い回す
本番の wp-config.php はリポジトリに置かず、ローカル専用のものを1つ用意して3サイトにマウントします。サイトごとの違いは、すべて環境変数で渡します。
define( 'DB_NAME', getenv( 'WORDPRESS_DB_NAME' ) ?: 'wordpress' );
define( 'DB_USER', getenv( 'WORDPRESS_DB_USER' ) ?: 'wordpress' );
define( 'DB_PASSWORD', getenv( 'WORDPRESS_DB_PASSWORD' ) ?: 'wordpress' );
define( 'DB_HOST', getenv( 'WORDPRESS_DB_HOST' ) ?: 'db' );
// 本番 DB のダンプに入っている URL を書き換えずに済むよう、URL は環境変数で上書きする
$base = getenv( 'WP_BASE_URL' ) ?: 'http://localhost:8080';
define( 'WP_HOME', $base );
define( 'WP_SITEURL', $base );
$debug = filter_var( getenv( 'WP_DEBUG' ), FILTER_VALIDATE_BOOLEAN );
define( 'WP_DEBUG', $debug );
define( 'WP_DEBUG_LOG', $debug );
define( 'WP_DEBUG_DISPLAY', false );
x-wordpress-env: &wordpress-env # 3サイト共通の環境変数
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: ${DB_USER:-wordpress}
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD:-wordpress}
WP_DEBUG: ${WP_DEBUG:-true}
services:
sub_a:
<<: *wordpress
environment:
<<: *wordpress-env
WORDPRESS_DB_NAME: sub_a
WP_BASE_URL: ${SUB_A_BASE_URL:-http://localhost:8082}
WP_HOME・WP_SITEURL を定数で上書きしておくと、本番の DB ダンプをそのまま取り込んでも、管理画面やリンクがローカルの URL で動きます。ダンプの中の URL を置換する必要がありません。
4. DB は初回起動時にサイトごとに作って取り込む
MariaDB の公式イメージは、初回起動時(データのボリュームが空のとき)に /docker-entrypoint-initdb.d/ のファイルを順に実行します。メインの DB は MYSQL_DATABASE とダンプのファイルで作り、サブサイトの DB はシェルスクリプトで作ります。
#!/bin/bash
# docker/db-init/20-sites.sh
set -e
SITES=(
"sub_a:/seed/sub_a/sub_a.sql.gz"
"sub_b:/seed/sub_b/sub_b.sql.gz"
)
for site in "${SITES[@]}"; do
db="${site%%:*}"; dump="${site#*:}"
mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" <<SQL
CREATE DATABASE IF NOT EXISTS \`${db}\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON \`${db}\`.* TO '${MYSQL_USER}'@'%';
SQL
if [ -f "${dump}" ]; then
gunzip -c "${dump}" | mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" "${db}"
else
echo "[db-init] 警告: ${dump} が見つかりません(${db} は空のまま)"
fi
done
ダンプが無くても起動は止まらず、空の DB ができます。ダンプには個人情報が含まれるので、Git では追跡せず、各自のディスクに置く運用にしています。
5. 本番のディレクトリ構成を前提にしたコードを、書き換えずに動かす
サブ A は、WordPress の外にも独自の PHP(共通のヘッダー、フォームなど)があり、本番の構成を前提にファイルを読み込んでいました。
本番: /sub-a/cms/ ← WP 本体
/sub-a/inc/ ← 共通の PHP
// テーマの functions.php
require_once ABSPATH . '../inc/common.php'; // /sub-a/cms/ の1つ上の inc/
// inc/parts-header.php(WP の外から WP を読み込む)
require_once dirname(__DIR__) . '/cms/wp-load.php'; // /sub-a/cms/wp-load.php
ローカルの公式イメージは、WordPress 本体がドキュメントルートの直下(/var/www/html/)にあり、cms/ がありません。本番のコードは変えたくないので、ローカル側で次の2つを用意しました。
ABSPATH . '../inc/' 用のシンボリックリンク
inc/ はドキュメントルート直下(/var/www/html/inc)にマウントしています。WordPress から見た ABSPATH . '../inc/' は /var/www/inc なので、そこにリンクを張ります。
RUN ln -s /var/www/html/inc /var/www/inc
realpath で解決されるので、inc/ の中で __FILE__ から計算しているサイトのルートも / になり、ローカルの URL と合います。ほかのサイトでは使わないので、ほかに影響はありません。
cms/wp-load.php の中継ファイル
<?php
// docker/sub-a-wp-load.php を /var/www/html/cms/wp-load.php にマウントする
require_once dirname( __DIR__ ) . '/wp-load.php';
- ./docker/sub-a-wp-load.php:/var/www/html/cms/wp-load.php:ro
6. メールは msmtp で Mailpit に集める(-f との衝突に注意)
ローカルからは外部にメールを送りたくないので、PHP の sendmail_path を msmtp にして、Mailpit のコンテナへ送ります。
# docker/msmtprc
defaults
tls off
auth off
account mailpit
host mailpit
port 1025
from wordpress@example.local
account default : mailpit
最初は sendmail_path に --read-envelope-from(ヘッダーの From を封筒の送信元に使う)を付けていました。ところが、サブ A のフォームが mb_send_mail(..., '-f 送信元') と -f で送信元を指定していて、msmtp が次のエラーで送信を拒否していました。
msmtp: cannot use both --from and --read-envelope-from
--read-envelope-from を外して、msmtp -t だけにすると解決しました。-f があればそれを、無ければ msmtprc の from を使ってくれます。
COPY msmtprc /etc/msmtprc
RUN printf 'sendmail_path = "/usr/bin/msmtp -t"\n' \
> /usr/local/etc/php/conf.d/zz-mail.ini
ちなみに、ローカルで wp_mail() が送信元を指定せずに false を返す場合は、msmtp ではなく WordPress 側の問題です。既定の送信元が wordpress@localhost になり、PHPMailer がアドレスとして不正と判断します。送信元を明示すれば届きます。
使い方
cp .env.example .env
docker compose up -d --build
| URL | |
|---|---|
| メイン | http://localhost:8080/ |
| サブ A / サブ B | http://localhost:8082/ ・ http://localhost:8083/ |
| phpMyAdmin | http://localhost:8081/ |
| Mailpit | http://localhost:8025/ |
DB ごと作り直すときは docker compose down -v && docker compose up -d --build です。
まとめ
- イメージは1つだけビルドして、3サービスで使い回す。WordPress のバージョンはイメージの中で固定する。
- コアは名前付きボリューム、開発するものだけバインドマウントする。
wp-config.phpはローカル専用を1つにして、違いは環境変数で渡す。WP_HOME・WP_SITEURLを上書きすれば、本番のダンプをそのまま使える。- 本番のパスを前提にしたコードは、シンボリックリンクと中継ファイルで吸収し、本番のコードは書き換えない。