話題の FrankenPHP を使用した WordPressの開発環境を作成してみました
Windows の WSL2 と Docker を使って構築する FrankenPHP による WordPress 開発環境を紹介します

Franken-wpdev WordPress 開発環境
以前に作成した Linux-Apache-Mariadb-Php (LAMP) や Linux-Nginx-Mariadb-Php (LEMP) を使った WordPress 開発用環境をベースに作成しています
これを FrankenPHP を使った最新の環境に置き換えてみました
https://github.com/php/frankenphp
Webサーバーが Caddy サーバーを使うようになりますが、ほとんど同じような構成です
話題の FrankenPHP を試してみたいという方におすすめです☺️
使用イメージ
Docker イメージは、Debian(bookworm) ベースのイメージを使用しています
- dunglas/frankenphp:1.7-php8.4-bookworm イメージをカスタマイズ
- Database MySql 8.4 公式イメージ
ダウンロード
FrankenPHP PHP8.4 版
ダウンロードボタンをクリックするとパスワード入力ページが表示されるので、パスワード( wpdev )を入力してダウンロードして下さい
パスワードを入力すると自動的にダウンロードが実行されます
ファイル構成
Windowsの任意のディレクトリフォルダーで franken-wpdev-x.x.x.zip を解凍すると下記ファイル構成に展開されます

php/ | Dockerfile | frankenphp イメージカスタムビルドファイル |
php.ini | php 定義ファイル(xdebug 定義含む) | |
php.ini.development | php 定義ファイルサンプル | |
php.ini.production | php 定義ファイルサンプル | |
Caddyfile | Caddy Webサーバー構成定義ファイル | |
server.crt | ※mkcert を使って自己証明書を作成して上書きコピーして置き換えてください | |
server.key | ※mkcert を使って自己証明書を作成して上書きコピーして置き換えてください | |
public/ | dbtest.php | データベース動作確認用 |
phpinfo.php | php 動作確認用 | |
wp-install.sh | wp-cli を使った WordPress インストール用のシェルファイル | |
.env | 構築する PHP バージョン, DB パスワード等を設定します | |
docker-compose.yml | franken-wpdev コンポーズファイル本体 | |
readme.txt | franken-wpdev コンポーズファイルの説明書 |
WSL2 の Linux(ubuntu等)からの実行について
Windowsのフォルダーから実行して Docker クライアントとファイル共有するのが簡単ですが、デメリットとして、Windows と Linux の異なるファイルシステム間でのファイル共有はファイルアクセスが遅くなるという影響があります
ひと手間かかりますが、ターミナルを使い WSL2 上の Ubuntu 等にログインして、 home ディレクトリのログインユーザー下に workspace 等の作業ディレクトリを作ってファイルを展開、そこから Docker で docker-compose.yml を実行することをおすすめします。
ターミナルでLinux(Ubuntu等)へのログイン後は、作業用ディレクトリへ移り、そこから code .
と入力するだけでVSCodeが起動出来るので、以降の作業は Windows 上での操作と同じ感覚で行えると思います
このように VSCode では、簡単に WSL Linux 上で実行出来るようになっているのですが、Windowsで使っているエディタや NetBeans 等では WSL 上のファイル操作に対応していないことが多いです。このような場合にはネットワークドライブを割り当てることで対応します(wsl$Ubuntu-20.04 をドライブ名 Z: 等に割当てドライブ名を使用してアクセスすることが可能-但し、一部のファイル(SQLiteのDB等)はネットワークドライブでアクセスが制限されることもあるようです)
.env ファイル
SERVER_NAME=localhost PROJECT_TZ=Asia/Tokyo PHP_VERSION=8.4 PHP_INI_DIR=/usr/local/etc/php NODE_VERSION=22.16 NODE_ENV=development DB_HOST=db DB_NAME=dbtest DB_PASSWORD=admin DB_ROOT_PASSWORD=admin DOCUMENT_ROOT=./public
docker-compose.yml ファイル
services: php: #image: dunglas/frankenphp:1.7-php8.3-bookworm # frankenphp custom Dockerfile build: context: './php/' args: PHP_VERSION: ${PHP_VERSION} NODE_VERSION: ${NODE_VERSION} ports: - "80:80" # HTTP - "443:443" # HTTPS - "443:443/udp" # HTTP/3 - "8080:8080" # node js develop - "5173:5173" # node js develop depends_on: - db volumes: # Volumes needed for Caddy certificates and configuration - ${DOCUMENT_ROOT}/:/app/public - caddy_data:/data - caddy_config:/config environment: - "PS1=[$(whoami):$(pwd)]$ " - SERVER_NAME=${SERVER_NAME:-localhost} #set SERVER_NAME, defaults to localhost - PROJECT_TZ=${PROJECT_TZ} # comment the following line in production, it allows to have nice human-readable logs in dev tty: true container_name: php db: image: mysql:8.4 command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci restart: always ports: - "3306:3306" volumes: - db_data:/var/lib/mysql environment: MYSQL_DATABASE: ${DB_NAME} MYSQL_PASSWORD: ${DB_PASSWORD} MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} TZ: ${PROJECT_TZ} container_name: db mailhog: image: mailhog/mailhog ports: - 8025:8025 - 1025:1025 container_name: mailhog volumes: caddy_data: caddy_config: db_data:
php – Dockerfile ファイル
ARG PHP_VERSION="" ARG NODE_VERSION="" FROM node:${NODE_VERSION:+${NODE_VERSION}-}bookworm AS node FROM dunglas/frankenphp:1.7-php${PHP_VERSION}-bookworm ENV TZ=${PROJECT_TZ} RUN apt-get update && apt-get install -y --no-install-recommends \ sudo git curl wget openssh-client libssl-dev default-mysql-client libsqlite3-dev ca-certificates && \ apt-get clean && rm -rf /var/cache/apt/archives/* && \ rm -rf /var/lib/apt/lists/* RUN install-php-extensions \ zip \ intl \ opcache \ shmop \ sockets \ bcmath \ exif \ gd \ pdo_mysql \ mysqli \ apcu \ xdebug #MailHog / mhsendmail RUN curl -sSLO https://github.com/mailhog/mhsendmail/releases/download/v0.2.0/mhsendmail_linux_amd64 \ && chmod +x mhsendmail_linux_amd64 \ && mv mhsendmail_linux_amd64 /usr/local/bin/mhsendmail #composer COPY --from=composer /usr/bin/composer /usr/bin/composer # node, npm, npx for JS dev COPY --from=node /usr/local/bin/node /usr/local/bin/ COPY --from=node /usr/local/lib/node_modules/ /usr/local/lib/node_modules/ RUN ln -s /usr/local/bin/node /usr/local/bin/nodejs \ && ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \ && ln -s /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx #wp-cli RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \ && chmod +x wp-cli.phar \ && mv wp-cli.phar /usr/local/bin/wp #php.ini COPY ./php.ini ${PHP_INI_DIR}/php.ini #caddy server config file COPY ./Caddyfile /etc/caddy/Caddyfile COPY ./server.crt /etc/caddy/certs/server.crt COPY ./server.key /etc/caddy/certs/server.key # Create user ARG USER_ID="1000" ARG GROUP_ID="1000" ARG USER="appuser" RUN groupadd --gid ${GROUP_ID} ${USER} \ && useradd --uid ${USER_ID} --gid ${GROUP_ID} -m ${USER} \ && echo "$USER ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/${USER} \ && chmod 0440 /etc/sudoers.d/${USER} RUN \ # Add additional capability to bind to port 80 and 443 setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \ # Give write access to /data and /config volumes for the app user # These directories are mounted from named volumes (caddy_data, caddy_config) # Ensure the user can write into these mount points. # Caddy will create subdirectories like /data/caddy and /config/caddy. mkdir -p /data/caddy /config/caddy && \ chown -R ${USER}:${USER} /data && \ chown -R ${USER}:${USER} /config USER ${USER} # Expose ports (good practice, though ports are mapped in compose.yaml) EXPOSE 80 443 443/udp # Set the default command to run FrankenPHP with Caddy # The base image dunglas/frankenphp often has CMD ["frankenphp", "run"] # Explicitly setting it can help ensure it runs as intended. #CMD sh -c "/usr/local/bin/frankenphp version && \ # echo '--- Loaded Caddy Modules ---' && \ # /usr/local/bin/frankenphp list-modules && \ # echo '--- Starting FrankenPHP ---' && \ # /usr/local/bin/frankenphp run --config /etc/caddy/Caddyfile" CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile"]
使用可能な画像ライブラリとして、GD をインストールしています。imagick はしばらく更新されてなく php8.4 環境へのインストールには問題があるようなのでインストールしていません
Caddyfile – Webサーバー構成ファイル
{ frankenphp { # 一般的なWebリクエスト(例: WordPressのページ表示)とは別に、長時間実行されるバックグラウンドタスクや、特定のエントリポイントを持つ常駐型アプリケーションを起動する場合に使用 # worker /path/to/your/custom_worker.php 2 # 2つのカスタムワーカーを起動 # WordPressのようなリクエスト駆動型のアプリケーションでは、php_server ディレクティブを使い、worker オプションを明示的に使う必要はない # num_threads を設定することで、指定した数のPHPワーカースレッドが起動し、並行してリクエストを処理する # 設定しない場合、FrankenPHPがデフォルト値(通常はCPUコア数などに基づいて決定)を使用 num_threads 4 # 4つのPHPワーカースレッドを起動 - CPUコア数や予想負荷に応じて調整 } # ディレクティブの評価順序 # php_server を file_server より先に評価することで、.php ファイルがPHPで処理されるようにする order php_server before file_server } (common_wp_config) { # Set the document root to /app/public, where your application files are served from. # This matches the volume mount in your compose.yaml: ${DOCUMENT_ROOT}/:/app/public root * /app/public # --- Gzip/Zstd 圧縮設定 --- # 画像ファイル (image/*) は対象外 encode zstd br gzip # FrankenPHP による PHP の処理 # グローバル設定 `order` により、.php ファイルはここで処理される # php_server は設定された `root` を基準にPHPファイルを探す # 例: /wordpress/index.php へのリクエストは /app/public/wordpress/index.php を実行 php_server # --- WordPress のパーマリンク設定 (/wordpress/ サブディレクトリ用) --- # /wordpress/以下のパスでファイルやディレクトリが存在しない場合に /wordpress/index.php を呼び出す @wpPermalinks { path /wordpress/* not file {path} # {path} はリクエストされたパス (例: /wordpress/my-page) not file {path}/ # ディレクトリでもない } # 一致した場合、リクエストを /wordpress/index.php に書き換える # 書き換えられたリクエストは再度Caddyによって処理され、php_serverがindex.phpを実行する rewrite @wpPermalinks /wordpress/index.php?{query} # --- 静的ファイル --- # CSS、JS、画像などの静的ファイル配信 file_server # --- ブラウザキャッシュ設定 (Expiresヘッダー) --- # HTMLファイル (1秒キャッシュ、再検証を要求) @cacheHtml header Content-Type text/html* header @cacheHtml Cache-Control "public, max-age=1, must-revalidate" # CSSファイル (60秒キャッシュ) @cacheCss header Content-Type text/css* header @cacheCss Cache-Control "public, max-age=60" # JavaScriptファイル (60秒キャッシュ) @cacheJs header Content-Type text/javascript* header @cacheJs Cache-Control "public, max-age=60" # 画像ファイル (1日キャッシュ - 86400秒) @cacheImages header Content-Type image/* header @cacheImages Cache-Control "public, max-age=86400" # --- その他のセキュリティヘッダー --- header X-Frame-Options "SAMEORIGIN" header X-Content-Type-Options "nosniff" # header Content-Security-Policy "default-src 'self'" # 必要に応じて設定 # --- ログ設定 --- # Caddyのデフォルトログは標準出力。ファイルに出力する場合: # log { # output file /var/log/caddy/access.log { # roll_size 10MiB # ログローテーション設定 # roll_keep 5 # roll_keep_for 720h # } # format console # または json など # } } # サイトアドレス (通常のブラウザアクセス用 - HTTPS) https://{$SERVER_NAME:localhost} { # --- TLS (SSL/HTTPS) 設定 --- # Caddy は公開ドメインに対して Let's Encrypt を使用して自動でHTTPS化 # localhost の場合は、自己署名証明書を動的に生成(有効期間12時間)して使用 (ブラウザで警告が出る) # 開発用に信頼された証明書を使いたい場合は `tls internal` を使用可 # この開発環境では有効期間とブラウザ警告を回避するため mkcert を使用して localhost 開発用の認証局(CA)を作成し、その CA によって署名された証明書(有効期間約2年3ヶ月)を使用する # 1. ホストマシン(windows 等): mkcert をインストールし、mkcert -install を実行 (初回のみ) # 2. ホストマシン: mkcert localhost 127.0.0.1 ::1 等で作成した証明書 (server.crt) と鍵 (server.key) を docker の php カスタム Dockerfile ファイルと同じフォルダー内にコピー # tls internal tls /etc/caddy/certs/server.crt /etc/caddy/certs/server.key import common_wp_config } # ループバックリクエスト用 (HTTP) # host.docker.internal への HTTP リクエストを処理 http://host.docker.internal { import common_wp_config }
ファイルの永続化について
Windows/WSL(Linux) 側とdocker コンテナ内との共有
- docker-compose.yml ファイルのあるフォルダ下の public フォルダーを docker 内の /app/public
Docker内での永続化
- データベース /var/lib/mysql をdocker 内で db_data 名で永続化
- Caddy サーバーのデータと構成を docker 内で /data/caddy と /config/caddy 名で永続化
使い方
準備
SSL(https) でアクセスするので Windows 側に mkcert をインストールして自己署名証明書を作成します
https://github.com/FiloSottile/mkcert
mkcert をインストールしたらwindows側に mkcert による証明書が信頼された証明書としてブラウザで警告が出ないように登録します
mkcert -install
下記のようなダイアログが表示されるので確認してインストールしてください

次にサーバー側に設定する自己証明書を作成します
同包されている自己証明書も使えますが、証明書には有効期間がありますので、念の為ご自身で作成して差し替えてください
mkcert localhost 127.0.0.1 ::1
作成された localhost+2-key.pem, localhost+2.pem を php フォルダー内 server.key, server.crt へ上書きコピーします
Caddy は公開ドメインに対して Let’s Encrypt を使用して自動でHTTPS化する機能があります。また、localhost の場合は、自己署名証明書を動的に生成してくれますが、その証明書を信頼された証明書として登録するのに少し手間がかかるので、この開発環境では mkcert を使用しています
実行
Docker desktop for windows が実行されている状態で powershell から docker-compose up を実行
VSCode に Container Tools 拡張機能を入れていれば docker-compose.ymlファイルを選択して右クリックからメニューで GUI 操作で出来るので、キーボード入力がほとんどいらなくなります

初回の起動時やベースイメージのPHPバージョンが変わった場合、例えば 8.4.0 が 8.4.1 になった場合等は、Dockerfile で追加している拡張機能のコンパイル等が行われるので時間がかかります

起動したら、エディタのコンテナアイコンをクリックすると詳細な情報の確認や各種操作がGUIで出来ます

ブラウザから下記URLにアクセスして動作確認します
https://localhost/phpinfo.php
https://localhost/dbtest.php

php とデータベースが正常に動作していることを確認したら、wordpress のインストールを行います
WordPress インストール
WordPress は wp-install.sh シェルスクリプトを実行すると自動的にドキュメントルート下の wordpress フォルダーへインストールされます
インストール条件を変えたい場合は、必要に応じて wp-install.sh 内のパラメータを変更してください
インストール先フォルダー名を変更する場合は、Caddyfile 内の該当箇所もあわせて変更してください
wp-install.sh ファイル
#!/bin/bash set -eu if [[ -f /app/public/wordpress/index.php ]]; then echo "Wordpress is already installed..." 2>&1 else #==================================================== declare DB_USERNAME=root declare DB_PASSWORD=admin declare DB_HOST=db declare OWNER_USER=appuser declare WP_VERSION=latest declare WP_DB_NAME=wordpress declare WP_INSTALLDIR=/app/public/wordpress declare WP_ROOT_URL=https://localhost/wordpress declare WP_SITE_TITLE=FrankenPhpTestSite declare WP_ADMIN_LOGIN=admin declare WP_ADMIN_PASSWORD=admin declare WP_ADMIN_EMAIL=example@example.com #==================================================== if [[ ! -d /app/public/wordpress ]]; then mkdir /app/public/wordpress fi echo "Download wordpress..." 2>&1 wp core download --path=${WP_INSTALLDIR} --version=${WP_VERSION} --locale=ja echo "wp config define create" cd ${WP_INSTALLDIR} # find ${WP_INSTALLDIR}/wp-content -type d -exec chmod 775 {} \; # find ${WP_INSTALLDIR} -type f -exec chmod 664 {} \; # WP_DEVELOPMENT_MODE は、開発内容により 'core'、'plugin'、'theme'、'all' 等を適宜指定する wp core config \ --dbname=${WP_DB_NAME} \ --dbuser=${DB_USERNAME} \ --dbpass=${DB_PASSWORD} \ --dbhost=${DB_HOST} \ --dbcharset=utf8mb4 \ --dbprefix=wp_ \ --extra-php <<PHP define( 'WP_DEBUG', true ); define( 'SCRIPT_DEBUG', false); define( 'WP_DEVELOPMENT_MODE', false ); define( 'WP_DEBUG_LOG', false ); PHP echo "Installing wordpress..." # wp db drop --yes wp db create wp core install \ --url=${WP_ROOT_URL} \ --title=${WP_SITE_TITLE} \ --admin_user=${WP_ADMIN_LOGIN} \ --admin_password=${WP_ADMIN_PASSWORD} \ --admin_email=${WP_ADMIN_EMAIL} echo "Rewriting permalink_structure" wp option update permalink_structure "/%postname%/" echo "Time zone setting" wp option update timezone_string 'Asia/Tokyo' wp option update date_format 'Y-m-d' wp option update time_format 'H:i' echo "plugin install..." wp plugin install wp-multibyte-patch --activate wp plugin install query-monitor wp plugin install wp-crontrol wp plugin install localhost2host-docker-internal wp plugin install cb-change-mail-sender wp plugin install wordpress-beta-tester wp plugin install yasakani-cache wp plugin install plugin-load-filter # wp plugin install loco-translate # wp plugin install contact-form-7 # wp plugin install classic-widgets # wp plugin install theme-check # wp plugin install plugin-check echo "theme install..." wp theme install hello-elementor echo "theme_unit_test install..." wp plugin install wordpress-importer --activate wget https://raw.githubusercontent.com/WPTT/theme-unit-test/master/themeunittestdata.wordpress.xml wp import themeunittestdata.wordpress.xml --authors=create # wget https://raw.githubusercontent.com/jawordpressorg/theme-test-data-ja/master/wordpress-theme-test-data-ja.xml # wp import wordpress-theme-test-data-ja.xml --authors=create fi exit 0
wp cli に関しては、下記記事が参考になります
インストール
- wp-install.sh のパラメータを必要に応じて修正
- franken-wpdev-php コンテナへ Attach Shell してターミナル操作
- ターミナルから public フォルダへ移り bash wp-install.sh 実行
- 設定内容に従って自動的に wp-cli によりインストールされます
franken-wpdev-php コンテナへ Attach Shell すると wp コマンドだけでなく、Node の npm, npx も使用できるようになっています
VSCode で Attach Shell するとそのコンテナに内の操作することができます
Docker では、ログインの代わりに、コンテナに Attach Shell することでターミナルから操作できるようになります

VSCode のターミナルが表示されるので bash wp-install.sh を実行します

public の下に wordpress ディレクトリが作られてインストールされます
動作確認
https://localhost/wordpress へアクセスすれば表示されるはずです
https://localhost/wordpress/wp-login.php で管理画面へログインできます
WordPress 管理者ユーザー
ユーザー : admin
パスワード: admin
次回以降は、既にインストールされている wordpress へアクセスすればOKです
HTTP/3 について
Caddyサーバーは、https を基本として、公開ドメインに対してLet’s Encryptを使用して自動でHTTPS証明書を取得・管理する機能があります。圧縮では zstd や br 等に対応できたり、簡単に HTTP/2 や HTTP/3 プロトコルに対応できるモダンで高性能なサーバーです
私の環境では、http3 にならなかったので生成AIといろいろやり取りしながら調べると
Dockerのポートマッピングや Windows の firewall 等の設定で UDPの443番ポートの許可は問題なかったのですが、dockerのログに failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 7168 kiB, got: 416 kiB) が記録されていました
これ以上の確認はしませんでしたが、linuxのカーネル側の調整がいるようです。いろいろ試してみてください
サイトヘルスチェック curl エラー対策について
Docker コンテナ内から localhost へアクセスするには、localhost の代わりに host.docker.internal へアクセスする必要があります
この docker 環境の事情により WordPress のサイトヘルスのループバックテストがエラーとなってしまいます
対応するために、Docker コンテナ内から localhost に対するループバックリクエストを host.docker.internal に置き換える localhost2host.docker.internal プラグインが使えます。既にインストールされているので、プラグイン管理ページから有効化してください
※ このプラグインは wp-env でも使えるのでご利用ください 😊
メール送信テストについて
ローカルでメールテストを行える MailHog (https://github.com/mailhog/MailHog) を利用できます
WordPressのメール送信 wp_mail 関数は、localhost サイト名では送信しない仕様(ドメイン名にピリオド以下の設定が不可欠)となっていますので WordPress plugin の CB Change Mail Sender を有効化してメール送信者情報を設定する必要があります
CB Change Mail Sender の送信者情報
CB Mail Sender Name : wpdev
CB Mail Sender Email: wpdev@localhost.dev
WordPress から送信されるメールは https://localhost:8025 へアクセスするとメール内容が確認できます

Xdebug について
デバッグは、VSCode でもできるみたいですが、ここは、私の使い慣れている NetBeans を使いたいので、php.ini には、NetBeans IDE 用の xdebug3 の設定をしてあります
[XDebug] ;xdebug3 settings xdebug.mode=debug ;xdebug.start_with_request=yes xdebug.start_with_request=trigger xdebug.discover_client_host=0 ;xdebug.client_host=localhost xdebug.client_host=host.docker.internal ;xdebug.log=/tmp/xdebug.log xdebug.client_port=9003 xdebug.idekey="netbeans-xdebug"
ポイントは、host.docker.internal を指定するところです
開発環境なので、Xdebug を使っており frankenphp の環境のレスポンスに特別の優位性は感じられません
単に WordPress を実行するだけでよいなら Xdebug をインストールせずに OPCache の JIT 機能を有効化すればパフォーマンスの向上が望めると思われます
NetBeans 側の設定

ポート番号とidkey を合わせておきます
franken-wpdev ディレクトリの public 下の wordpress をプロジェクトとして登録します


XAMPPだったらここまででよかったのですが、Docker desktop for windows の場合はもう一つマッピングの設定が必要となります
WSLのLinux(Ubuntu等)上にDockerコンテナと共有するwordpressフォルダを作成していて WindowsのNetBeans からその共有フォルダにアクセスするには、ネットワークドライブを割り当てておく必要があります
プロジェクトのプロパティから Run Configuration を指定して、Advanced ボタンをクリックします

表示された画面で、Docker 内の wordpress パスと Windows 内の wordpress パスを指定します

以上で、デバッグできるようになるのでブレークポイントを設定して確認してみて下さい
データベースへのアクセスについて
データベースの操作は、HeidiSQL について紹介します
難しい設定などはなく XAMPP 環境と同様な感じの設定であっさり接続できました
セッションマネージャーで接続ホスト名(localhost)とユーザーを登録します
データベース管理者ユーザー
ユーザー : root
パスワード: admin

開くをクリックするとこんな感じの表示となるので、後は自由にデータベースを操作することができます

以上
簡単に FrankenPHP Caddy の環境が構築できるので、いろいろ設定を変えて遊んでみてください (^^)