WP-CLI+WordPress+DB+データ専用コンテナをdocker composeで｜ローカル環境構築ハンズオン

# 前提
WordPressサイトのオリジナルテーマ作成運用、プラグイン開発運用などに使用している環境になります。WordPressはドメインを投稿時の画像URLなどに書き込む特性があるのでドメインもローカルで同じものが使えるように設定します。こうすることで自動デプロイがやりやすくなります。DBは本番から手動エクスポートしてダウンロードしDBの同期はしていません。（個人的には必要な時にダウンロードする形であまり困ったことはありません。）

なお今回作成するサイトのドメインはexample.comとして進めていきますが
適宜、皆様の環境に合わせて変えてみてください。
このドメインに関しては本番用としてドメインレジストラから取得しているもの
架空で設定するものなど好きに設定しても大丈夫です。
※このあたりよくわからない方は実在しないであろう乱数字的なドメインを
設定すればどこかに影響が出る可能性を下げられるため安心かと思います。
例）weoriuewqryoiuv6786234kiuywerkhjqwerhiasudyfqweryui197j.com

# この環境を使うことで感じているメリット
Docker,Linux,nginx,apacheなどのサーバ関連の勉強になります。
いわゆるバーチャルマシン環境よりかなりデータ量も軽量で立ち上がりも早いです。（控えめにいっても体感で数倍は違います）
Docker,Linux,nginx,apacheなどのサーバ関連デファクトスタンダードな環境で開発ができます。
デファクトスタンダードなので共有できる知見を持っている人も多いです。

:::note warn
以下は[local](https://localwp.com/)というツールでも簡単にできるものです。
とりあえず簡単にローカル環境を構築したいという方は[local](https://localwp.com/)をお勧めします。
サーバ環境をより知りたい方はDockerもお勧めです。
:::

PHPやMySQLのバージョンを変えて気軽にテストができます。
プラグイン、WordPressのバージョン違いのテスト、バージョンアップ＆ダウンが簡単にできます。
チーム開発する際などに同じ環境を簡単に共有、再現することができます。

# 予想されるデメリット
Docker,Linux,nginx,apacheなどのサーバ関連の知識が必要になるので
コーダーの方やデザイナーの方は導入する際の学習コストは高めかもしれません。
それゆえ前提知識がないとブラックボックス化するのでトラブルがあった際にはまります。

# コンテナ構成(ローカル環境)
nginxプロキシ(このコンテナだけ１つ作成して複数サイト間で使いまわしています)
↓
WP-CLIコンテナ
WordPressコンテナ
↓
MySQLコンテナ
↓
データ専用コンテナ

WP-CLIコンテナ以下はサイトごとにこのような構成をそれぞれ作っています。これは現在運用中のサイトが複数あり、使っているプラグインが違っていたり、WordPressのコアバージョンが違っていたりするのでその辺りの検証を個別にできるようにするために分けています。今回の構成からもう少し抽象度を上げて組めばURLだけ変えたらサイトを切り替えるなんてこともできそうですがそれをやったらやったで運用上混乱が起きる（環境どうしの蜜結合が起こってくる）と思うので現在の構成にしています。

# 運用環境構成
ローカル環境（今回はここのみ、MacOS前提で進めます）
↓
GitHub
GitHubAction
↓
本番サーバ

# nginxプロキシコンテナ設定ファイルの作成
プロキシサーバとはその名の通り代理でブラウザの代わりにサーバ（ここではWordPressコンテナ）にアクセスしてくれるという役割を担っています。

ここではブラウザからのアクセスを一旦プロキシに送るようにします。それにはローカルPC（ホストOS）のhostsファイル(/private/etc/hosts)を編集します。これによって普段example.comにアクセスしたらexample.comの本番サーバに通信されるのをローカルPC内のnginxプロキシに向けられるようになります。
以下をhostsファイルの最下部に追記します。

```
0.0.0.0 example.com
```

これで保存をしてexample.comにアクセスするとローカルPC内のプロキシを見に行くようになります。
元のようにように本番に見にいくようにするには上記の行をコメントアウトして保存します。

```
# 0.0.0.0 example.com
```

それではまずプロキシコンテナを格納するディレクトリを好きな名前で任意の場所に作成します。
そしてディレクトリ直下に以下のファイルを作成します。

docker-compose.yml
```
version: "3.7"
services:
  proxy:
    image: jwilder/nginx-proxy
    container_name: proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./nginx:/etc/nginx
      - ./certs:/etc/nginx/certs:ro
    restart: always
    logging:
      options:
        max-size: 5m
        max-file: "10"
volumes:
  shared-volume:
    name: shared
networks:
  default:
    name: shared
```

ymlについての詳細は以下の記事をご参照ください。

https://qiita.com/SwuBHj8aKGqBKHet/items/2f3ddc1038e4c90e9931

これでプロキシコンテナの設定は完了です。

# WP-CLI,WordPress,MySQLコンテナ設定ファイルの作成
まずはサイトのローカル環境を格納するディレクトリを好きな名前で任意の場所に作成します。
そしてディレクトリ直下に以下のファイルを作成します。

docker-compose.yml
```
version: "3.7"
services:

  wpcli:
    image: wordpress:cli
    container_name: wpcli
    volumes:
      - example-wp:/var/www/html
      - ./tmp:/tmp
    command: chown xfs:xfs /tmp/wpcli-entrypoint.sh &&
             sh /tmp/wpcli-entrypoint.sh mysql exampleuser examplepass
    user: root
    networks:
      - default
    environment:
      WORDPRESS_DB_HOST: mysql
      WORDPRESS_DB_USER: exampleuser
      WORDPRESS_DB_PASSWORD: examplepass
      WORDPRESS_DB_NAME: wordpress

  mysql:
    platform: linux/x86_64
    image: mysql:5.7.37
    volumes:
      - example-dbdatavolume:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: wordpress
      MYSQL_USER: exampleuser
      MYSQL_PASSWORD: examplepass
    restart: always
    networks:
      - default
    logging:
      options:
        max-size: 5m
        max-file: "10"

  wordpressimage:
    image: wordpress:latest
    volumes:
      - example-wp:/var/www/html #デフォルトで作成されるvolumeをわかりやすくするための設定
      - ./tmp:/tmp
      - ./etc/vhost-example.conf:/etc/apache2/conf.d/vhost-example.conf
      - ./etcphp/php.ini:/usr/local/etc/php/php.ini
      - ./wp-content:/var/www/html/wp-content
    container_name: example-wp-ct
    environment:
      VIRTUAL_HOST: example.com
      WORDPRESS_DB_HOST: mysql
      WORDPRESS_DB_USER: exampleuser
      WORDPRESS_DB_PASSWORD: examplepass
      WORDPRESS_DB_NAME: wordpress
      CERT_NAME: default
    depends_on:
      mysql:
        condition: service_started
    restart: always
    tty: true
    networks:
      - default
    logging:
      options:
        max-size: 5m
        max-file: "10"

volumes:
  example-wp:
  example-dbdatavolume:
    external: true #事前にデータ専用コンテナを作成（compose管理外のコンテナを探す指定）

networks:
  default:
    name: shared

#volumeの構成
#shared用に１つ、これは複数サイト共用。
#wp用に１つ。サイトごとに１つ生成される。
#データ専用コンテナ１つ。サイトごとに１つ生成。

```

少し長いですが基本的にはプロキシで指定していたのと同じくdocker-compose.ymlでの設定です。
やっていることとしては
WP-CLI(Wordpressのバージョンアップダウン、プラグインのバージョンアップダウンなどがコマンドで簡単にできるツール)
Wordpress本体
MySQLサーバ
のコンテナを一気に順番を制御して立ち上げています。
順番に関しては以下のような理由があります。
MySQLが一番立ち上がるのに時間がかかります。
WP-CLIでWordPressのインストールを実行するのにMySQLが立ち上がっていないとエラーになります。
これらの理由から順番を制御する必要があります。詳細は後述する[tmpディレクトリの項でシェルスクリプトを作成](https://qiita.com/ryomoucmei/items/0770f9e19f67c9c82bcb#tmp%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA-1)しているのでそちらをご参照ください。

ここのdocker-compose.ymlではプロキシでは使っていなかった項目があるので少し補足していきます。

## WordPressコンテナ
wordpressimage:
この要素ではWordPressコンテナを作成しています。

### volumes要素
```
volumes:
    - example-wp:/var/www/html #デフォルトで作成されるvolumeをわかりやすくするための設定
    - ./tmp:/tmp
    - ./etc/vhost-example.conf:/etc/apache2/conf.d/vhost-example.conf
    - ./etcphp/php.ini:/usr/local/etc/php/php.ini
    - ./wp-content:/var/www/html/wp-content
```

volumesというのはDockerがファイル管理をする際に使用する仕組みのことです。
主にはデータを保持するためにホストOS（ローカルPCのOS自分の場合はMacOS）のディレクトリに
Dockerからデータを保存したり、逆にホストOSから読み込んだりしています。

volumeはコンテナを生成したら一緒に作成されここでは
exapmle-wpという名前をつけてvolumeを生成しています。
（名前をつけないと乱数時で生成されるので個人的には管理しやすくするため名前をつけています）
/var/www/htmlはDockerコンテナ内のパスでこのディレクトリをまるっとホストに持ってきて保存します。
exapmle-wpというvolumeがホストOS内にあれば2回目からはそのホストOS内のディレクトリを
コンテナ内に読み込んで使用するというイメージです。
これはいわゆるVolumeと言われるマウントのやり方になります。

./tmp:/tmp以下の３つも基本的には同じくホストOSにディレクトリを作って
その中のファイルをDocker内に読み込むということをしています。
こうすることでコンテナを削除したりしてもデータはホストOS側にあるので消えなくなります。
そしてこの３つはバインドマウント（short syntax）という手法になります。
VolumeマウントではDockerが自動的にホストOS内にディレクトリを作成していますが
バインドマウントではユーザーが自分でディレクトリを作成して管理します。
今回はymlファイルと同階層にtmp,etc,etcphp,wp-contentのディレクトリを配置しています。

#### tmpディレクトリ
ここにはシェルスクリプトのファイルを置きます。
そしてDockerコンテナを立ち上げた時に実行してWP-CLIコマンドを使って
WordPressのインストール作業を自動化しています。

#### etcディレクトリ
ここにはapacheで使用するバーチャルホストのファイルを指定しています。
バーチャルホストを使用することで指定のドメインでapache経由で指定のディレクトリを表示することができます。

#### etcphpディレクトリ
こちらはphpのXdebugを使用する際にphp.iniファイルを編集する必要があったので設置しています。
あとWordPressのファイルアップロードのデータ上限を２Mより上げたい場合もphp.iniファイルで設定できます。
必要なければ削除しても良いと思います。

#### wp-content
WordPressのディレクトリですね。プラグイン、テーマ、アップロードファイルなどが格納されています。私のケースではプラグイン、テーマを開発しているのでまるっとバインドマウントしています。gitもこのディレクトリ以下で管理しています。人によってはプラグインディレクトリだけ、テーマディレクトリだけというのもあるかもしれません。

### environment要素
environmentは環境変数です。環境変数とは
ざっくりいうとサーバ内で使える変数というイメージです。

```
environment:
    VIRTUAL_HOST: example.com
    WORDPRESS_DB_HOST: mysql
    WORDPRESS_DB_USER: exampleuser
    WORDPRESS_DB_PASSWORD: examplepass
    WORDPRESS_DB_NAME: wordpress
    CERT_NAME: default
```

#### VIRTUAL_HOST
今回のプロジェクトのURLを指定します。
これは前述のプロキシサーバが読みにきます。

#### WORDPRESS_DB_HOST
DBサーバのホスト名を設定します。

#### WORDPRESS_DB_USER
DBのユーザー名を設定します。

#### WORDPRESS_DB_PASSWORD
DBのパスワードを設定します。

#### WORDPRESS_DB_NAME
DBの名前を設定します。

WORDPRESS_DBとついているものはwordpress公式イメージで設定されている変数です。

https://hub.docker.com/_/wordpress

#### CERT_NAME
SSL証明書の設定

### network要素
これは一番上の階層のnetwork要素のdefault、つまりsharedという名前のネットワークの中に所属しますよという指定です。
Dockerコンテナは基本的に同じネットワーク内のコンテナは自動で繋がるようになり、後述するWP-CLI,MySQLコンテナも
同じネットワークにすることで通信ができるようになります。

```
networks:
    - default
```

## WP-CLIコンテナ
wpcli:
この要素ではWP-CLIコンテナを作成しています。

### volumes要素
```
volumes:
    - example-wp:/var/www/html
```
example-wpというのはWordPressコンテナで指定していたvolumeです。
これはホストOS上に作成されたvolumeディレクトリ（example-wp）を
WP-CLIコンテナ内の/var/www/htmlに呼び込んで使用するという意味になります。
つまり一つのホストOS上のディレクトリを２つのコンテナで共有していることになります。
ここではhtml直下にWordPressのファイルが展開されています。
そしてWP-CLIではインストール時にwp-config.phpなどを生成したり
そこにDBの情報を書き込んだりしてセットアップをコマンドラインから行うことができます。

#### tmpディレクトリ
```
- ./tmp:/tmp
```

tmpディレクトリには自分で作成するシェルスクリプトを設置します。
ここでは以下のシェルを置きます。


```
#!/bin/ash

set -e

host="$1"
shift
user="$1"
shift
password="$1"
shift
cmd="$@"

echo "Waiting for mysql"
until mysql -h"$host" -u"$user" -p"$password" &> /dev/null
do
       >&2 echo -n "."
       sleep 1
done

>&2 echo "MySQL is up - executing command"
#ここまでがmysqlコンテナが立ち上がるのを待つためのスクリプト

if [ ! -e '/check' ]; then
    touch /check
    cd /var/www/html
    rm /var/www/html/wp-config.php
    wp config create --allow-root --dbname=wordpress --dbuser=exampleuser --dbpass=examplepass --dbhost=mysql --dbprefix=wp_
    sed -i -e "98a \$_SERVER['HTTPS'] = 'on';" wp-config.php
    chown www-data:www-data /var/www/html/wp-config.php
    wp core install --allow-root --url="example.com" --title="example-title" --admin_user="xxxxxxxxxxxxxxx" --admin_password="xxxxxxxxxxxxxxxxxx" --admin_email="example-docker@gmail.com" --path="/var/www/html"
    wp rewrite structure --allow-root "%postname%" --url="example.com" --path="/var/www/html"
    chown -R www-data:www-data /var/www/html/wp-content/
    wp option update --allow-root home 'https://example.com'
    wp option update --allow-root siteurl 'https://example.com'
    tail -f /var/log/serverlog
else
    # 2回目以降
    tailf /var/log/serverlog
fi
```

ざっくり流れを説明しますと
最初の行から
&2 echo "MySQL is up - executing command"の行までが
MySQLコンテナが立ち上がるのを待つためのスクリプトになります。
MySQLコンテナが立ち上がらない状態でWordPressのインストールなどを
WP-CLIで実行するとWordPressコンテナからMySQLコンテナを操作しようとするためエラーになります。

そのあとは初回（WordPressが設定されていない状態）のみ
WordPressの設定をするということになります。
これはいわゆる最初ブラウザからインストールの設定をすると思いますが
あの作業をWP-CLIを使ってコマンドから自動化しています。
そのほかにもwp-config.phpにするHTTPSの設定、
パーマリンクの変更などを行っています。

前述のシェルスクリプトをコピペする際はドメイン、admin_user、admin_passwordなどを適宜変えてご使用ください。

### command要素
コマンド要素ではdocker起動時に実行するコマンドを以下のように
コマンド形式、シェルスクリプトファイルを指定して実行できます。

```
command: chown xfs:xfs /tmp/wpcli-entrypoint.sh &&
         sh /tmp/wpcli-entrypoint.sh mysql exampleuser examplepass
```

まずバインドマウントしたシェルスクリプトファイルのパーミッションを
WordPressコンテナのwpルート以下の実行ユーザーwww-dataに合わせています。
WordPressコンテナのディストリビューションはDebianでユーザーwww-dataはuid、gid共に33です。
一方WP-CLIコンテナのディストリビューションはAlpineでユーザーxfsがuid、gid共に33です。
パーミッションを合わせてから前述のシェルスクリプトを実行しています。
mysql exampleuser examplepassはシェルスクリプトに渡している値で
シェルスクリプト内に読み込まれて変数に代入されて使用されます。

### user要素
こちらはWP-CLIコンテナ内のディストリビューションAlpine Linuxを
どのユーザーで実行するかを指定しており、この記述でxfsで実行するようになります。

```
user: xfs
```

### networks要素
これはWordPressコンテナで説明したのと同じ指定です。
所属するネットワークを指定しています。

```
networks:
    - default
```

### environment要素
こちらもWordPressコンテナで指定したものと同じです。
WP-CLIでMySQLを操作することもあるので必要になります。

```
    environment:
      WORDPRESS_DB_HOST: mysql
      WORDPRESS_DB_USER: exampleuser
      WORDPRESS_DB_PASSWORD: examplepass
      WORDPRESS_DB_NAME: wordpress
```

## MySQLコンテナ
mysql:
ここではMySQLコンテナを作成しています。

### platform要素
platform要素はM1チップを使用しているMacで必要になります。
CPUがintelの場合は削除します。

```
    platform: linux/x86_64
```

### volumes
ここで指定しているのは後述するデータ専用コンテナのvolumeになります。まだvolume自体ない状態です。
このようにデータベースコンテナとデータを格納するコンテナ（データ専用コンテナ）を分けることで
データベースのバージョン違いのテストやデータベース自体を変更してのテストなどがやりやすくなります。

```
volumes:
    - example-dbdatavolume:/var/lib/mysql
```

## データ専用コンテナ
データ専用コンテナはbusyboxという軽量のコマンド集、ソフトウェアとのことでした。
以下のコマンドでコンテナが立ち上がり同時にコンテナ内に入った状態になります。
このコマンドを実行することでexample-dbdatavolumeというvolumeも作成されます。

```
docker run -i -t --name example-dbdatacontainer --mount source=example-dbdatavolume,target=/var/lib/mysql busybox
```

余談ですがls -laコマンドでルートのディレクトリ構成を見てみるとLinuxとほぼ同じような構成になっています。
ここではコンテナ内で特にやることはないので以下のコマンドでコンテナを抜けます。

```
exit
```

ここまで設定できたら準備完了です。

# コンテナ立ち上げ
それではコンテナを立ち上げましょう。

## プロキシコンテナ立ち上げ
まずはターミナルを立ち上げホストOS上でプロキシコンテナ用に作成したディレクトリに移動します。
そして以下のコマンドでコンテナを立ち上げます。

```
docker compose up -d
```

これでプロキシサーバが立ち上がりました。

## CLI＋アプリ＋DBコンテナ立ち上げ
次にローカル環境として作成したディレクトリに移動して以下のコマンドを実行します。

```
docker compose up -d
```

これでCLI＋アプリ＋DBコンテナが立ち上がりました。
データ専用コンテナはExited状態になっていると思いますがこれで機能してくれます。

# ブラウザからアクセス
前半設定したhostsファイルの設定を確認して
example.comなど設定したアドレスでブラウザにアクセスしてみます。
証明書の警告が出ますが今回は自分で作成した環境（0.0.0.0）にアクセスしているので
そのままアクセスするリンク（ボタンやリンクなどを押さないと出てきません）をクリックします。
これでWordPressが表示されれば完了です。

# 管理画面に入るには
example.com/wp-adminにアクセスします。
ユーザー名、パスワードはシェルスクリプトの
wp core installの行で指定したもので入れます。

# 似たサイトを複数立ち上げる場合｜プラグインインストールなども自動化
今回の手順通り進めるとホストOSのローカル環境として作成したディレクトリに
wp-contentディレクトリが自動的に作成されていると思います。
これはWordPressコンテナの以下の行

```
    - ./wp-content:/var/www/html/wp-content
```

でこのように指定されているため
ホストOS上にディレクトリがない場合はコンテナ内のディレクトリを
ホストOS上にコピーしてくれます。
つまりWordPress初期で設定されるwp-contentディレクトリがコピーされます。

ただいつも使うプラグインやベースとなるテーマがある場合は
wp-contentディレクトリをあらかじめホストOS上に置いておくと
逆にホストOSからdockerコンテナ内にコピーしてくれます。

するとプラグインのダウンロードなどもスキップできます。
WP-CLIのシェルスクリプトにコマンドを足せば
プラグインアップデートなどを自動化することも可能です。

# dockerコンテナが立ち上がらない場合
コンテナが立ち上がらなくなる場合も結構あります。
原因は様々ですがまずは立ち上げ方を変えるとプロセスが見えるようになります。

```
docker compose up
```

オプションdを外すとバックグラウンドではなくログがリアルタイムで
表示されるようになるので結構便利です。

ターミナルで新しいタブをひらけばdockerコマンドも普通に使えます。

# 環境を共有したい場合
基本的には今回作成した２つのディレクトリ
プロキシのディレクトリと
CLI+WP+DBのディレクトリをコピーして
別PCに移動してdocker compose up
データ専用コンテナも同じようにdocker runで作成すれば共有できると思います。

# 後書き
最近M1Macを導入したこともあり環境を見直してみたところ
WP-CLIコンテナも組み込むことができたのでまとめてみました。
（以前は自分でシェルスクリプトをねじこんでました）
使い方が間違っている部分、わかりづらい部分、もっと効率化できそうな部分などは
ご指摘いただけたら嬉しいです。

私がこの環境を導入している一番のモチベーションはサーバ環境の知識を勉強するというのが一番かもしれません。導入時に簡易ツール（MAMPやLocal）なども少し調査しましたが、当時MySQLのバージョンが変えられなかったり、SQL言語が固定だったりというところがありました。この辺はツールも進化していくのでそれを待てるかどうかは個々人の好みだと思います。個人的な経験としてMySQLのバージョンが上がった際、某レンタルサーバがMariaDBを採用した際、構築時にShellScriptで自動化する際などは今回の構成で獲得できた知識が活きたなと感じています。ともかく低レイヤーに行くほど自由度は上がるという法則はありますのでお好みでやり方やツールを選ぶのが良いと思います。