Dockerを用いたWordpressテーマ・プラグイン開発環境構築

## Introduction

Wordpressのテーマやプラグインを開発するため、Dockerを用いて環境構築を行います。
DockerコンテナでWP-CLIを用いることで開発における汎用性を考慮します。

公式のイメージはあくまで公開を想定しているようでしたので、
あくまでテーマやプラグインの開発をしやすくなるように構築します。

## Goal

docker-composeで構築し再利用や構成拡張が容易となるようにします。
構成は以下とし、少ない手順でWordpressに関する環境構築ができるようにします。

- nginx
- php
- MySQL
- phpMyAdmin
- Mailhog

データベースとWordpress関連のファイルはホストにマウントし、
コンテナが破棄されてもデータを残すようにします。

## Support

以下環境で動作を確認しています。

- MacOSX 10.14 Mojave
    - Docker for Mac (18.06-ce-mac73)
- CentOS 7.5
    - Docker 18.06-ce
    - docker-compose 1.22.0

## How to

### ファイル構成

```text
~/wordpress-docker
|- .docker
|  |- app
|  |  `- Dockerfile
|  `- web
|     |- etc/nginx
|     |  |- h5bp
|     |  |  |- directive-only
|     |  |  |  |- cross-domain-insecure.conf
|     |  |  |  |- extra-security.conf
|     |  |  |  `- x-ua-compatible.conf
|     |  |  `- location
|     |  |     |- cache-busting.conf
|     |  |     |- cross-domain-fonts.conf
|     |  |     |- expires.conf
|     |  |     `- protect-system-files.conf
|     |  |- fastcgi_params
|     |  |- mime.types
|     |  |- nginx.conf
|     |  `- site.conf.template
|     `- Dockerfile
|- .utils
|  |- message.sh
|  `- wpinst.sh
|- Makefile
`- docker-compose.yml
```

### Docker

#### docker-compose.yml

使用するコンテナ情報を記載します。
各コンテナのenvironmentとして`VIRTUAL_HOST`を指定している箇所があります。
これは`jwilder/nginx-proxy`によるリバースプロキシの設定です。

なお、nginx-proxyは便利ですがdocker.sockを読み判断をするため注意が必要です。
当記事は開発環境として他のコンテナが立ち上がっていないことを想定しています。
別のコンテナが立っている状態であったり、本番環境に対して当記事の内容を参照頂く場合は、
そのまま使用せず各リスクに対しての検討を行ってください。

```yml:docker-compose.yml
version: '2'
services:

  ################################
  # Proxy
  ################################
  proxy:
    image: jwilder/nginx-proxy
    ports:
      - 80:80
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro

  ################################
  # Application
  ################################
  app:
    build:
      context: ./.docker/app
    depends_on:
      - db
    links:
      - db
    volumes:
      - ./app:/var/www/html/
    environment:
      - DB_NAME=wp
      - DB_USER=wp
      - DB_PASSWORD=password
      - DB_HOST=db
      - DB_PREFIX=wp_
      - WP_ENV=development
      - WP_HOST=$PROJECT_NAME.docker
      - WP_ADMIN_USER=webmaster
      - WP_ADMIN_PASSWORD=5iveL!fe

  ################################
  # Webserver
  ################################
  web:
    build:
      context: ./.docker/web
    depends_on:
      - app
    links:
      - app
    volumes_from:
      - app
    environment:
      - VIRTUAL_HOST=$PROJECT_NAME.docker
      - VIRTUAL_PORT=80
    command: /bin/sh -c "envsubst '$$VIRTUAL_HOST' < /etc/nginx/site.conf.template > /etc/nginx/site.conf && nginx -g 'daemon off;'"

  ################################
  # Database
  ################################
  db:
    image: mariadb
    volumes:
      - ./db/mysql_data:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=wp
      - MYSQL_USER=wp
      - MYSQL_PASSWORD=password

  ################################
  # phpMyAdmin
  ################################
  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    depends_on:
      - db
    links:
      - db
    environment:
      - VIRTUAL_PORT=80
      - VIRTUAL_HOST=phpmyadmin.$PROJECT_NAME.docker
      - PMA_ABSOLUTE_URI=http://phpmyadmin.$PROJECT_NAME.docker/
      - PMA_ARBITRARY=1
      - PMA_HOST=db
      - PMA_PORT=3306
      - PMA_USER=wp
      - PMA_PASSWORD=password

  ################################
  # Mailhog
  ################################
  mail:
    image: mailhog/mailhog
    environment:
      - VIRTUAL_PORT=8025
      - VIRTUAL_HOST=mail.$PROJECT_NAME.docker
```

#### Dockerfile(Wordpress)

Wordpress本体を配置するコンテナはphp7をベースに以下を追加しています。

- mhsendmail
- mysql-client
- wpcli

```dockerfile:./docker/app/Dockerfile
FROM webdevops/php:alpine-php7

RUN apk add --no-cache musl-dev go mysql-client

# install mhsendmail and configure it to user mailhog
RUN go get github.com/mailhog/mhsendmail
RUN cp /root/go/bin/mhsendmail /usr/bin/mhsendmail
RUN echo 'sendmail_path = /usr/bin/mhsendmail --smtp-addr mail:1025' > /opt/docker/etc/php/php.ini

# install wp cli
RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
RUN chmod +x wp-cli.phar
RUN mv wp-cli.phar /usr/local/bin/wp

WORKDIR /var/www/html/
```

#### Dockerfile(nginx)

nginxのイメージをベースに、後述する設定ファイル群を追加しています。

```dockerfile:.docker/web/Dockerfile
FROM amd64/nginx:alpine

RUN apk add --no-cache curl
ADD ./etc/nginx/* /etc/nginx/
ADD ./etc/nginx/h5bp/location/* /etc/nginx/h5bp/location/
ADD ./etc/nginx/h5bp/directive-only/* /etc/nginx/h5bp/directive-only/
```

設定ファイルはファイル数、各ファイルの行数がそれなりにあるので折りたたんでいます。

<details><summary>.docker/web/etc/nginx.conf</summary><div>

```nginx:.docker/web/etc/nginx.conf
# Configuration File - Nginx Server Configs
# http://nginx.org/en/docs/dirindex.html

# Run as a unique, less privileged user for security reasons.
# Default: nobody nobody
user nginx;

# Sets the worker threads to the number of CPU cores available in the system for best performance.
# Should be > the number of CPU cores.
# Maximum number of connections = worker_processes * worker_connections
# Default: 1
worker_processes auto;

# Maximum number of open files per worker process.
# Should be > worker_connections.
# Default: no limit
worker_rlimit_nofile 8192;

events {
  # If you need more connections than this, you start optimizing your OS.
  # That's probably the point at which you hire people who are smarter than you as this is *a lot* of requests.
  # Should be < worker_rlimit_nofile.
  # Default: 512
  worker_connections 8000;
}

# Log errors and warnings to this file
# This is only used when you don't override it on a server{} level
# Default: logs/error.log error
error_log  /var/log/nginx/error.log warn;

# The file storing the process ID of the main process
# Default: nginx.pid
pid        /run/nginx.pid;

http {
  
  # Hide nginx version information.
  # Default: on
  server_tokens off;
  
  # Setup the fastcgi cache.
  fastcgi_buffers 8 8k;
  fastcgi_buffer_size 8k;
  fastcgi_read_timeout 120s;
  fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=wordpress:10m max_size=250m inactive=1h;
  fastcgi_cache_use_stale updating error timeout invalid_header http_500;
  fastcgi_cache_lock on;
  fastcgi_cache_key $realpath_root$scheme$host$request_uri$request_method$http_origin;
  fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
  fastcgi_pass_header Set-Cookie;
  fastcgi_pass_header Cookie;
  
  # Specify MIME types for files.
  include       mime.types;

  # Default: text/plain
  default_type  application/octet-stream;
  
  # Update charset_types to match updated mime.types.
  # text/html is always included by charset module.
  # Default: text/html text/xml text/plain text/vnd.wap.wml application/javascript application/rss+xml
  charset_types
    text/css
    text/plain
    text/vnd.wap.wml
    application/javascript
    application/json
    application/rss+xml
    application/xml;
  
  # Include $http_x_forwarded_for within default format used in log files
  log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
  
  # Log access to this file
  # This is only used when you don't override it on a server{} level
  # Default: logs/access.log combined
  access_log /var/log/nginx/access.log main;
  
  # How long to allow each connection to stay idle.
  # Longer values are better for each individual client, particularly for SSL,
  # but means that worker connections are tied up longer.
  # Default: 75s
  keepalive_timeout 20s;
  
  # Speed up file transfers by using sendfile() to copy directly
  # between descriptors rather than using read()/write().
  # For performance reasons, on FreeBSD systems w/ ZFS
  # this option should be disabled as ZFS's ARC caches
  # frequently used files in RAM by default.
  # Default: off
  sendfile        on;
  
  # Don't send out partial frames; this increases throughput
  # since TCP frames are filled up before being sent out.
  # Default: off
  tcp_nopush      on;
  
  # Compression

  # Enable gzip compression.
  # Default: off
  gzip on;

  # Compression level (1-9).
  # 5 is a perfect compromise between size and CPU usage, offering about
  # 75% reduction for most ASCII files (almost identical to level 9).
  # Default: 1
  gzip_comp_level    5;

  # Don't compress anything that's already small and unlikely to shrink much
  # if at all (the default is 20 bytes, which is bad as that usually leads to
  # larger files after gzipping).
  # Default: 20
  gzip_min_length    256;

  # Compress data even for clients that are connecting to us via proxies,
  # identified by the "Via" header (required for CloudFront).
  # Default: off
  gzip_proxied       any;

  # Tell proxies to cache both the gzipped and regular version of a resource
  # whenever the client's Accept-Encoding capabilities header varies;
  # Avoids the issue where a non-gzip capable client (which is extremely rare
  # today) would display gibberish if their proxy gave them the gzipped version.
  # Default: off
  gzip_vary          on;

  # Compress all output labeled with one of the following MIME-types.
  # text/html is always compressed by gzip module.
  # Default: text/html
  gzip_types
    application/atom+xml
    application/javascript
    application/json
    application/ld+json
    application/manifest+json
    application/rss+xml
    application/vnd.geo+json
    application/vnd.ms-fontobject
    application/x-font-ttf
    application/x-web-app-manifest+json
    application/xhtml+xml
    application/xml
    font/opentype
    image/bmp
    image/svg+xml
    image/x-icon
    text/cache-manifest
    text/css
    text/plain
    text/vcard
    text/vnd.rim.location.xloc
    text/vtt
    text/x-component
    text/x-cross-domain-policy;

  include site.conf;
}
```

</div></details>

<details><summary>.docker/web/etc/nginx/site.conf.template</summary><div>

```nginx:.docker/web/etc/nginx/site.conf.template
server {
  listen [::]:80;
  listen 80;

  server_name ${VIRTUAL_HOST};

  access_log   /var/log/nginx/access.log main;
  error_log    /var/log/nginx/error.log;
  
  root  /var/www/html;
  index index.php index.htm index.html;
  add_header Fastcgi-Cache $upstream_cache_status;

  # Specify a charset
  charset utf-8;

  # Set the max body size equal to PHP's max POST size.
  client_max_body_size 25m;

  # https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#virtualbox
  sendfile off;

  # Prevent PHP scripts from being executed inside the uploads folder.
  location ~* /app/uploads/.*\.php$ {
    deny all;
  }
  
  location / {
    try_files $uri $uri/ /index.php?$args;
  }
  
  include h5bp/directive-only/extra-security.conf;
  include h5bp/directive-only/x-ua-compatible.conf;
  include h5bp/location/cross-domain-fonts.conf;
  include h5bp/location/protect-system-files.conf;
  
  location ~ \.php$ {
    try_files $uri /index.php;

    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
    fastcgi_param DOCUMENT_ROOT $realpath_root;
    fastcgi_pass app:9000;
  }
}

# Redirect some domains
server {
  listen [::]:80;
  listen 80;
  server_name www.${VIRTUAL_HOST};

  location / {
    return 301 http://${VIRTUAL_HOST}$request_uri;
  }
}
```

</div></details>

<details><summary>.docker/web/etc/nginx/fastcgi_params</summary><div>

```nginx:.docker/web/etc/nginx/fastcgi_params
fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  REQUEST_SCHEME     $scheme;
fastcgi_param  HTTPS              $https if_not_empty;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;
```

</div></details>

<details><summary>.docker/web/etc/nginx/mime.types</summary><div>

```nginx:.docker/web/etc/nginx/mime.types
types {

  # Data interchange

    application/atom+xml                  atom;
    application/json                      json map topojson;
    application/ld+json                   jsonld;
    application/rss+xml                   rss;
    application/vnd.geo+json              geojson;
    application/xml                       rdf xml;


  # JavaScript

    # Normalize to standard type.
    # https://tools.ietf.org/html/rfc4329#section-7.2
    application/javascript                js;


  # Manifest files

    application/manifest+json             webmanifest;
    application/x-web-app-manifest+json   webapp;
    text/cache-manifest                   appcache;


  # Media files

    audio/midi                            mid midi kar;
    audio/mp4                             aac f4a f4b m4a;
    audio/mpeg                            mp3;
    audio/ogg                             oga ogg opus;
    audio/x-realaudio                     ra;
    audio/x-wav                           wav;
    image/bmp                             bmp;
    image/gif                             gif;
    image/jpeg                            jpeg jpg;
    image/jxr                             jxr hdp wdp;
    image/png                             png;
    image/svg+xml                         svg svgz;
    image/tiff                            tif tiff;
    image/vnd.wap.wbmp                    wbmp;
    image/webp                            webp;
    image/x-jng                           jng;
    video/3gpp                            3gp 3gpp;
    video/mp4                             f4p f4v m4v mp4;
    video/mpeg                            mpeg mpg;
    video/ogg                             ogv;
    video/quicktime                       mov;
    video/webm                            webm;
    video/x-flv                           flv;
    video/x-mng                           mng;
    video/x-ms-asf                        asf asx;
    video/x-ms-wmv                        wmv;
    video/x-msvideo                       avi;

    # Serving `.ico` image files with a different media type
    # prevents Internet Explorer from displaying then as images:
    # https://github.com/h5bp/html5-boilerplate/commit/37b5fec090d00f38de64b591bcddcb205aadf8ee

    image/x-icon                          cur ico;


  # Microsoft Office

    application/msword                                                         doc;
    application/vnd.ms-excel                                                   xls;
    application/vnd.ms-powerpoint                                              ppt;
    application/vnd.openxmlformats-officedocument.wordprocessingml.document    docx;
    application/vnd.openxmlformats-officedocument.spreadsheetml.sheet          xlsx;
    application/vnd.openxmlformats-officedocument.presentationml.presentation  pptx;


  # Web fonts

    application/font-woff                 woff;
    application/font-woff2                woff2;
    application/vnd.ms-fontobject         eot;

    # Browsers usually ignore the font media types and simply sniff
    # the bytes to figure out the font type.
    # https://mimesniff.spec.whatwg.org/#matching-a-font-type-pattern
    #
    # However, Blink and WebKit based browsers will show a warning
    # in the console if the following font types are served with any
    # other media types.

    application/x-font-ttf                ttc ttf;
    font/opentype                         otf;


  # Other

    application/java-archive              ear jar war;
    application/mac-binhex40              hqx;
    application/octet-stream              bin deb dll dmg exe img iso msi msm msp safariextz;
    application/pdf                       pdf;
    application/postscript                ai eps ps;
    application/rtf                       rtf;
    application/vnd.google-earth.kml+xml  kml;
    application/vnd.google-earth.kmz      kmz;
    application/vnd.wap.wmlc              wmlc;
    application/x-7z-compressed           7z;
    application/x-bb-appworld             bbaw;
    application/x-bittorrent              torrent;
    application/x-chrome-extension        crx;
    application/x-cocoa                   cco;
    application/x-java-archive-diff       jardiff;
    application/x-java-jnlp-file          jnlp;
    application/x-makeself                run;
    application/x-opera-extension         oex;
    application/x-perl                    pl pm;
    application/x-pilot                   pdb prc;
    application/x-rar-compressed          rar;
    application/x-redhat-package-manager  rpm;
    application/x-sea                     sea;
    application/x-shockwave-flash         swf;
    application/x-stuffit                 sit;
    application/x-tcl                     tcl tk;
    application/x-x509-ca-cert            crt der pem;
    application/x-xpinstall               xpi;
    application/xhtml+xml                 xhtml;
    application/xslt+xml                  xsl;
    application/zip                       zip;
    text/css                              css;
    text/csv                              csv;
    text/html                             htm html shtml;
    text/markdown                         md;
    text/mathml                           mml;
    text/plain                            txt;
    text/vcard                            vcard vcf;
    text/vnd.rim.location.xloc            xloc;
    text/vnd.sun.j2me.app-descriptor      jad;
    text/vnd.wap.wml                      wml;
    text/vtt                              vtt;
    text/x-component                      htc;

}
```

</div></details>

<details><summary>.docker/web/etc/nginx/h5bp/location/cross-domain-fonts.conf</summary><div>

```nginx:.docker/web/etc/nginx/h5bp/location/cross-domain-fonts.conf
# Cross domain webfont access
location ~* \.(?:ttf|ttc|otf|eot|woff|woff2)$ {
  include h5bp/directive-only/cross-domain-insecure.conf;

  # Also, set cache rules for webfonts.
  #
  # See http://wiki.nginx.org/HttpCoreModule#location
  # And https://github.com/h5bp/server-configs/issues/85
  # And https://github.com/h5bp/server-configs/issues/86
  access_log off;
  add_header Cache-Control "max-age=2592000";
}
```

</div></details>

<details><summary>.docker/web/etc/nginx/h5bp/location/expires.conf</summary><div>

```nginx:.docker/web/etc/nginx/h5bp/location/expires.conf
# Expire rules for static content

# No default expire rule. This config mirrors that of apache as outlined in the
# html5-boilerplate .htaccess file. However, nginx applies rules by location,
# the apache rules are defined by type. A consequence of this difference is that
# if you use no file extension in the url and serve html, with apache you get an
# expire time of 0s, with nginx you'd get an expire header of one month in the
# future (if the default expire rule is 1 month). Therefore, do not use a
# default expire rule with nginx unless your site is completely static

# cache.appcache, your document html and data
location ~* \.(?:manifest|appcache|html?|xml|json)$ {
  add_header Cache-Control "max-age=0";
}

# Feed
location ~* \.(?:rss|atom)$ {
  add_header Cache-Control "max-age=3600";
}

# Media: images, icons, video, audio, HTC
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|mp4|ogg|ogv|webm|htc)$ {
  access_log off;
  add_header Cache-Control "max-age=2592000";
}

# Media: svgz files are already compressed.
location ~* \.svgz$ {
  access_log off;
  gzip off;
  add_header Cache-Control "max-age=2592000";
}

# CSS and Javascript
location ~* \.(?:css|js)$ {
  add_header Cache-Control "max-age=31536000";
  access_log off;
}

# WebFonts
# If you are NOT using cross-domain-fonts.conf, uncomment the following directive
# location ~* \.(?:ttf|ttc|otf|eot|woff|woff2)$ {
#  add_header Cache-Control "max-age=2592000";
#  access_log off;
# }
```

</div></details>

<details><summary>.docker/web/etc/nginx/h5bp/location/protect-system-files.conf</summary><div>

```nginx:.docker/web/etc/nginx/h5bp/location/protect-system-files.conf
# Prevent clients from accessing hidden files (starting with a dot)
# This is particularly important if you store .htpasswd files in the site hierarchy
# Access to `/.well-known/` is allowed.
# https://www.mnot.net/blog/2010/04/07/well-known
# https://tools.ietf.org/html/rfc5785
location ~* /\.(?!well-known\/) {
  deny all;
}

# Prevent clients from accessing to backup/config/source files
location ~* (?:\.(?:bak|conf|dist|fla|in[ci]|log|psd|sh|sql|sw[op])|~)$ {
  deny all;
}
```

</div></details>

<details><summary>.docker/web/etc/nginx/h5bp/directive-only/cross-domain-insecure.conf</summary><div>

```nginx:.docker/web/etc/nginx/h5bp/directive-only/cross-domain-insecure.conf
# Cross domain AJAX requests

# http://www.w3.org/TR/cors/#access-control-allow-origin-response-header

# **Security Warning**
# Do not use this without understanding the consequences.
# This will permit access from any other website.
#
add_header "Access-Control-Allow-Origin" "*";

# Instead of using this file, consider using a specific rule such as:
#
# Allow access based on [sub]domain:
#    add_header "Access-Control-Allow-Origin" "subdomain.example.com";
```

</div></details>

<details><summary>.docker/web/etc/nginx/h5bp/directive-only/extra-security.conf</summary><div>

```nginx:.docker/web/etc/nginx/h5bp/directive-only/extra-security.conf
# The X-Frame-Options header indicates whether a browser should be allowed
# to render a page within a frame or iframe.
add_header X-Frame-Options SAMEORIGIN always;

# MIME type sniffing security protection
#	There are very few edge cases where you wouldn't want this enabled.
add_header X-Content-Type-Options nosniff always;

# The X-XSS-Protection header is used by Internet Explorer version 8+
# The header instructs IE to enable its inbuilt anti-cross-site scripting filter.
add_header X-XSS-Protection "1; mode=block" always;

# with Content Security Policy (CSP) enabled (and a browser that supports it (http://caniuse.com/#feat=contentsecuritypolicy),
# you can tell the browser that it can only download content from the domains you explicitly allow
# CSP can be quite difficult to configure, and cause real issues if you get it wrong
# There is website that helps you generate a policy here http://cspisawesome.com/
# add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' https://www.google-analytics.com;" always;
```

</div></details>

<details><summary>.docker/web/etc/nginx/h5bp/directive-only/x-ua-compatible.conf</summary><div>

```nginx:.docker/web/etc/nginx/h5bp/directive-only/x-ua-compatible.conf
# Force the latest IE version
add_header "X-UA-Compatible" "IE=Edge";
```

</div></details>

<details><summary>.docker/web/etc/nginx/h5bp/location/cache-busting.conf</summary><div>

```nginx:.docker/web/etc/nginx/h5bp/location/cache-busting.conf
# Built-in filename-based cache busting

# https://github.com/h5bp/html5-boilerplate/blob/5370479476dceae7cc3ea105946536d6bc0ee468/.htaccess#L403
# This will route all requests for /css/style.20120716.css to /css/style.css
# Read also this: github.com/h5bp/html5-boilerplate/wiki/cachebusting
# This is not included by default, because it'd be better if you use the build
# script to manage the file names.
location ~* (.+)\.(?:\d+)\.(js|css|png|jpg|jpeg|gif)$ {
  try_files $uri $1.$2;
}
```

</div></details>

### Makefile

docker-composeやDockerfileに環境変数`PROJECT_NAME`の記載があります。
Makefileでは環境変数の値を定義した後、各ターゲットで処理を切り替えています。
内容は読解できる範疇と思慮しますので、文面での説明は割愛します。

```makefile:Makefile
ARGS = $(filter-out $@,$(MAKECMDGOALS))
MAKEFLAGS += --silent


################################
# ENVIRONMENT
################################

export PROJECT_NAME=your_project
export COMPOSE_PROJECT_NAME=${PROJECT_NAME}

################################
# UTILS
################################

ssh:
	docker exec -it $$(docker-compose ps -q $(ARGS)) sh

wpinst:
	docker cp ./.utils/wpinst.sh $$(docker-compose ps -q app):/var/www/html/wpinst.sh
	docker exec -it $$(docker-compose ps -q app) sh wpinst.sh
	docker exec -it $$(docker-compose ps -q app) rm wpinst.sh

################################
# CONTAINER ACCESS
################################

up:
	bash ./.utils/message.sh info "Starting your project..."
	mkdir -p app
	mkdir -p db/mysql_data
	docker-compose up -d

stop:
	bash ./.utils/message.sh info "Stopping your project..."
	docker-compose stop

down:
	bash ./.utils/message.sh info "Removing containers..."
	docker-compose down

destroy: stop
	bash ./.utils/message.sh info "Deleting all containers..."
	docker-compose down --rmi all --remove-orphans

upgrade:
	bash ./.utils/message.sh info "Upgrading your project..."
	docker-compose pull
	docker-compose build --pull
	make composer update
	make up

restart: stop up

rebuild: destroy upgrade

clean:
	docker ps -f name=${PROJECT_NAME} -q | xargs docker stop && yes | docker container prune
	sudo rm -rf ./app/*
	sudo rm -rf ./db/mysql_data/*

################################
# INFORMATION
################################

urls:
	bash ./.utils/message.sh headline "You can access your project at the following URLS:"
	bash ./.utils/message.sh link "Backend:      http://${PROJECT_NAME}.docker/wp/wp-admin/"
	bash ./.utils/message.sh link "Frontend:     http://${PROJECT_NAME}.docker/"
	bash ./.utils/message.sh link "Mailhog:      http://mail.${PROJECT_NAME}.docker/"
	bash ./.utils/message.sh link "PHPMyAdmin:   http://phpmyadmin.${PROJECT_NAME}.docker/"
	echo ""

state:
	docker-compose ps

logs:
	docker-compose logs -f --tail=50 $(ARGS)


################################
# Argument fix workaround
################################
%:
	@:
```

<details><summary>.utils/message.sh</summary><div>

```bash:.utils/message.sh
#!/usr/bin/env bash
# arguments
messageType=$1
messageText=$2

# colors
RED='\033[0;31m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
YELLOW='\033[1;33m'
GRAY='\033[0;37m'
NC='\033[0m'

case "$messageType" in
    "headline")
        echo ""
        echo -e "${GRAY}-------------------------------------------------------------------${NC}"
        echo -e "${GREEN} $messageText${NC}"
        echo -e "${GRAY}-------------------------------------------------------------------${NC}"
        echo ""
    ;;
    "warning")
        echo -e "${RED}$(echo $messageText | tr '[:lower:]' '[:upper:]')${NC}"
    ;;
    "success")
        echo -e "${GREEN}$messageText${NC}"
    ;;
    "text")
        echo -e "${GRAY}$messageText${NC}"
    ;;
    "info")
        echo -e "${YELLOW}$messageText${NC}"
    ;;
    "link")
        echo -e "${CYAN}$messageText${NC}"
    ;;
esac
```

</div></details>

以下は`make wpinst`を実行した際に呼び出されるwpinst.shの内容です。
WP-CLIを用いてWordpressのダウンロードおよびインストール、マルチサイト化、日本語化、プラグインやテーマのインストールを行っています。

プラグインは概ね必要になるであろうもの、テーマはシングルカラムでの確認がしやすいよう[Cenote](https://wordpress.org/themes/cenote/)を入れるようにしています。
また、テーマ開発での利便性を考慮して日本語のダミー記事をインポートしています。

**注意点**
WP-CLIのコマンドオプションとして`--allow-root`を指定しています。
Dockerコンテナの実行ユーザがrootとなるためです。

```bash:.utils/wpinst.sh
#!/bin/sh

wp --info

wp core download --locale=ja --allow-root
wp config create        \
  --dbname=$DB_NAME     \
  --dbuser=$DB_USER     \
  --dbpass=$DB_PASSWORD \
  --dbhost=$DB_HOST     \
  --dbprefix=$DB_PREFIX \
  --allow-root
wp db create --allow-root
wp core multisite-install                 \
  --url="http://$WP_HOST"                 \
  --title=$WP_HOST                        \
  --admin_user=$WP_ADMIN_USER             \
  --admin_password=$WP_ADMIN_PASSWORD     \
  --admin_email="$WP_ADMIN_USER@$WP_HOST" \
  --allow-root

wp plugin install wp-multibyte-patch       --activate-network --allow-root
wp plugin install gutenberg                --activate-network --allow-root
wp plugin install all-in-one-seo-pack      --activate-network --allow-root
wp plugin install google-sitemap-generator --activate-network --allow-root
wp plugin install broken-link-checker      --activate-network --allow-root
wp plugin install wp-mail-smtp             --activate-network --allow-root
wp plugin install pushpress                --activate-network --allow-root

wp theme install cenote --activate --allow-root

wp option update timezone_string $(wp eval "echo _x( '0', 'default GMT offset or timezone string' );" --allow-root) --allow-root
wp option update date_format $(wp eval "echo __( 'Y-m-d' );" --allow-root) --allow-root

curl -LOk https://raw.github.com/jawordpressorg/theme-test-data-ja/master/wordpress-theme-test-date-ja.xml \
  && wp plugin install wordpress-importer --activate --allow-root \
  && wp import wordpress-theme-test-date-ja.xml --authors=create --allow-root \
  && rm wordpress-theme-test-date-ja.xml
```

### 実行

さいごにhostsを指定します。
$PROJECT_NAMEを`wpsample`とした場合は以下のように設定します。

```hosts
127.0.0.1 wpsample.docker
127.0.0.1 mail.wpsample.docker
127.0.0.1 phpmyadmin.wpsample.docker
```

これで準備はできました。

`make up`でDockerコンテナを立ち上げます。

`make wpinst`でWordpressを初期状態にします。
このときDBコンテナの準備が掛かるので`make logs`でDBのコンテナが立ち上がりきっていることを確認してから実行します。

ブラウザからhostsで指定したホスト名に対してアクセスを試みるとWordpressサイトが確認できます。

`make stop`でコンテナの停止、
`make destroy`でコンテナの破棄、
`make clean`でコンテナとマウントしたボリュームデータを破棄できます。

## Conclusion

Wordpressの環境構築としてXAMPPやVCCW、Trellisなどがありますが、
よりホスト環境を汚さず、容易に構築およびメンバーへの連携ができることが望ましいです。
この方法であれば効率化が図れ、拡張性もあるのではと思慮します。

いわゆるDevOpsとしては不充分であるため、本番・運用も鑑みてブラッシュアップしていきます。
うまくいったら更新します。その場合はk8sを用いていると思いますので別記事になるかもしれません。

## Reference

[GitHub - schliflo/bedrock-docker](https://github.com/schliflo/bedrock-docker)
[nginx-proxy(docker)を使う際の注意点 | TechBlog](https://tech.moiraibusiness.com/?p=183)