2026年8月2日
2026年8月2日
WordPressをDockerコンテナで動かす本番環境の構築手順
WordPressをDockerで本番運用するなら、公式の wordpress イメージとMySQLを Docker Compose でまとめ、HTTPSはリバースプロキシに任せ、データは名前付きボリュームに置き、データベースは外部に公開しない、という構成が基本になります。この記事では、ローカル開発用の構成をそのまま本番に持ち込んだときに起きやすい問題を避けながら、VPS上で本番環境を組み立てる手順を説明します。バックアップと更新の運用まで含めて、公開後に困らない状態を目指します。
ローカル用の構成と本番用の構成の違い
ローカル開発では、ソースコードをバインドマウントしてすぐ編集でき、データベースのポートもPCから直接つなげるようにしておくのが便利です。本番では、その便利さがそのままリスクになります。主な違いは次のとおりです。
- データベースのポートはホストに公開せず、コンテナ間のネットワークだけで通信する
- パスワードは compose.yaml に直書きせず、シークレットや権限を絞ったファイルで渡す
- イメージのタグを固定し、意図しないバージョンアップを防ぐ
- HTTPSの終端、ログの肥大化対策、再起動ポリシーを設定する
- データ(アップロードファイルとDB)のバックアップ手段を最初から用意する
この記事では、VPSにDocker Engine と Docker Compose プラグイン(docker compose コマンド)が入っている前提で進めます。既存のサイトをDocker環境へ移す場合は、移行前にファイルとデータベースのフルバックアップを取り、まず別のサーバーやステージング用ドメインで動作を確かめてからDNSを切り替えてください。
ディレクトリ構成
作業用ディレクトリを1つ作り、設定ファイルをまとめます。
/srv/wordpress/
├── compose.yaml
├── Caddyfile
├── php/
│ └── custom.ini
└── secrets/
├── db_password.txt
└── db_root_password.txt
パスワードファイルは、推測されにくい長いランダム文字列にします。openssl rand -base64 32 > secrets/db_password.txt のように生成できます。
compose.yaml を作る
WordPress本体、MySQL、HTTPSを担当するCaddyの3つのコンテナで構成します。
services:
db:
image: mysql:8.4
restart: unless-stopped
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD_FILE: /run/secrets/db_password
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
secrets:
- db_password
- db_root_password
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 10
networks:
- backend
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
wordpress:
image: wordpress:php8.3-apache
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password
WORDPRESS_CONFIG_EXTRA: |
define( 'WP_ENVIRONMENT_TYPE', 'production' );
define( 'DISALLOW_FILE_EDIT', true );
secrets:
- db_password
volumes:
- wp_data:/var/www/html
- ./php/custom.ini:/usr/local/etc/php/conf.d/custom.ini:ro
networks:
- backend
- frontend
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- frontend
secrets:
db_password:
file: ./secrets/db_password.txt
db_root_password:
file: ./secrets/db_root_password.txt
volumes:
db_data:
wp_data:
caddy_data:
caddy_config:
networks:
frontend:
backend:
internal: true
それぞれの設定の意図を補足します。
イメージのタグ固定: latest を使うと、docker compose pull のたびにPHPやMySQLのメジャーバージョンが変わる可能性があります。wordpress:php8.3-apache や mysql:8.4 のように、少なくともPHPとDBのバージョンが分かるタグを指定します。利用できるタグは時期によって変わるため、Docker Hubの公式イメージのページで確認してください(2026年10月現在)。
パスワードの渡し方: 公式の wordpress イメージと mysql イメージは、環境変数名の末尾に _FILE を付けると、値をファイルから読み込む仕組みに対応しています。Composeの secrets で渡したファイルは、コンテナ内の /run/secrets/ に配置されます。環境変数に直接パスワードを書くと docker inspect で見えてしまうため、ファイル経由のほうが漏れにくくなります。
ネットワークの分離: backend を internal: true にしているため、DBコンテナは外部と通信できず、WordPressコンテナからだけ到達できます。DBの ports: は書きません。
依存関係: depends_on に condition: service_healthy を付けると、MySQLの起動確認が取れてからWordPressが起動します。初回起動時にDBの初期化が終わる前にWordPressが接続しに行き、「データベース接続確立エラー」になる問題を避けられます。
ログのローテーション: Dockerの既定のログはサイズ上限がなく、長期運用でディスクを圧迫することがあります。max-size と max-file で上限を設けます。
PHPの設定とHTTPS
php/custom.ini には、アップロード上限などを書きます。公式PHPイメージは /usr/local/etc/php/conf.d/ 内の .ini を読み込みます。
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
max_execution_time = 120
expose_php = Off
HTTPSはCaddyに任せます。Caddyfile にドメインを書くだけで、Let's Encrypt などから証明書を自動で取得・更新します。事前にドメインのDNSをVPSのIPアドレスに向け、80番と443番のポートを開けておいてください。
example.com {
reverse_proxy wordpress:80
}
リバースプロキシの後ろにWordPressを置くと、WordPressからはHTTPで接続されているように見えます。公式イメージの wp-config.php には、X-Forwarded-Proto ヘッダーが https のときにHTTPSとして扱う処理が含まれているため、Caddyの既定の動作と組み合わせればそのまま動くことが多いです。管理画面でリダイレクトがループする場合は、この部分の挙動を確認してください。
起動と初期設定
権限を確認してから起動します。
cd /srv/wordpress
chmod 700 secrets
chmod 644 secrets/*.txt
docker compose up -d
docker compose ps
docker compose logs -f wordpress
シークレットのファイルは、コンテナ内のPHPやMySQLのプロセスが読める必要があります。ファイル自体を 600 にすると読めずに接続エラーになることがあるため、ここではディレクトリ側を 700 にしてホスト上の他ユーザーから守っています。
docker compose ps で3つのコンテナが起動し、db が healthy になっていれば、ブラウザで https://example.com を開いてWordPressのインストール画面に進みます。
本番でつまずきやすい点
ファイアウォールとDockerのポート公開
UFWなどでポートを閉じていても、Dockerが ports: で公開したポートは、Dockerが自前で追加するiptablesのルールによってファイアウォールを経由せずに開いてしまうことがあります。公開してよいのは80と443だけにし、それ以外のサービスは ports: を書かないか、"127.0.0.1:8080:80" のようにローカルホストに限定してください。
WordPress本体の更新
公式イメージは、/var/www/html のボリュームが空のときだけWordPressのファイルをコピーします。そのため、イメージを新しくしてもボリューム内のWordPress本体は更新されません。コア・プラグイン・テーマは、通常のサイトと同じく管理画面またはWP-CLIで更新します。イメージの更新は、PHPやApacheのセキュリティ修正を取り込む目的で定期的に行います。
docker compose pull
docker compose up -d
WP-CLIを使う
Apache版のイメージにはWP-CLIが入っていません。公式の wordpress:cli 系イメージを使うサービスを compose.yaml の services: に追加し、profiles で通常の up の対象から外しておくと、必要なときだけ実行できます。
wpcli:
image: wordpress:cli-php8.3
profiles: ["tools"]
user: "33:33"
depends_on:
- db
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
volumes:
- wp_data:/var/www/html
networks:
- backend
- frontend
docker compose run --rm wpcli wp plugin list
wp-config.php は環境変数からDB接続情報を読む形で作られているため、WP-CLI側のコンテナにも同じ環境変数とシークレットを渡します。user: "33:33" は、Apache版イメージ(Debian系)でファイルを所有する www-data のUIDに合わせるための指定です。合っていないと、プラグインの更新などで書き込みに失敗します。
バックアップと復元の確認
データは db_data と wp_data の2つのボリュームにあります。DBは稼働中にダンプを取り、ファイルはボリュームをtarで固めます。
mkdir -p backup
docker compose exec -T db sh -c \
'exec mysqldump -uroot -p"$(cat /run/secrets/db_root_password)" --single-transaction wordpress' \
> backup/db-$(date +%F).sql
docker run --rm \
-v wordpress_wp_data:/data:ro \
-v "$(pwd)/backup":/backup \
alpine tar czf /backup/wp-files-$(date +%F).tar.gz -C /data .
ボリューム名には、Composeのプロジェクト名(通常はディレクトリ名)が接頭辞として付きます。実際の名前は docker volume ls で確認してください。バックアップはVPSの外(オブジェクトストレージや別サーバー)にも保存し、別環境で復元できることを一度試しておくと、障害時に慌てずに済みます。
まとめ
- 公式の wordpress・mysql イメージを Docker Compose でまとめ、タグを固定する
- パスワードは
_FILE変数とシークレットで渡し、DBは内部ネットワークだけに置く - HTTPSはCaddyなどのリバースプロキシで終端し、ログには上限を設ける
- 公開ポートは80と443だけにし、Dockerがファイアウォールを迂回する点に注意する
- WordPress本体の更新は管理画面やWP-CLIで行い、イメージの更新はPHPなどの修正の取り込みに使う
- DBダンプとボリュームのバックアップを取り、復元まで試しておく