Edited at

Docker Compose で PHP 7.0 の開発環境を構築する

More than 1 year has passed since last update.

PHP 7.0 の開発環境を構築するために書いた Docker Compose の設定ファイルをまとめました。


Docker について

Docker はコンテナと呼ばれる単位でアプリケーションを管理するソフトウェアです。

コンテナはサーバー OS にインストールされているソフトウェアと独立しているので、サーバーの環境を壊すことなく、PHP のバージョンアップや HTTP サーバーの切り替えを手軽に試すことができます。

Linux ディストリビューションのパッケージ管理ツールの apt-get や yum をより使いやすくしたものとして見ることができるでしょう。

オーバーヘッドが少ないので、さくら VPS、DigitalOcean や Vultr などの格安の VPS ホスティングサービスで使うことができます。

DigitalOcean と Vultr はコンテナの運用に特化した CoreOS が選択肢として提供しています。CoreOS はコンテナを運用するための最小限のソフトウェアで構成されており、アップデートが自動化されています。

さくら VPS で利用するには自分で OS のイメージをアップロードしてインストールする必要があります。筆者の調査では、2GB プランでの動作を確認しました。512MB プランではインストールできませんでした。

コンテナのもとになるイメージは Docker Hub、Quay.io 、Tutum などの Docker Registory に保存して、開発環境と運用環境のあいだで共有したり、不特定多数の人に向けて配布することができます。


Docker Compose について

Docker Compose は YAML の設定ファイルを通じて複数のコンテナを管理するためのコマンドツールです。複数の設定ファイルを組み合わせることができます。


Alpine Linux を導入すべきか?

Docker 社が Alpine Linux の開発者を雇用し、公式イメージを Ubuntu から Docker に切り替えることがメディアで報道されました (Publickey の2016年2月の記事)。Alpine Linux は 5MB の軽量ディストリビューションです。

Alpine Linux を採用するメリットは転送時間やビルドにかかる時間を改善できることです。デメリットはマイナー OS なので、公開されているイメージの数はかぎられていることです。PHP や nginx などの公式イメージの一部が Alpine Linux への対応を始めています。

docker-compose.yml の例はこちらの記事をご参照ください。Alpine Linux の Vagrant Box の導入に関してはこちらの記事をご参照ください。


検証環境


  • Max OS X Yosemite

  • Virtualbox 5.0.4

  • Vagrant 1.7.4

  • Docker 1.8.2

  • Ubuntu 14.04 LTS (Docker のホスト OS)


docker-compose


使い方

docker-compose を実行するには docker-compose.yml のあるディレクトリに移動します。イメージのビルドとコンテナを起動させるには次のコマンドを実行します。

docker-compose build

docker-compose up -d

--build オプションを指定することで、コンテナのビルドとサービスの起動を同時に実行することもできます。

docker-compose up -d --build

サービスを起動している間に修正した設定ファイルを反映させるために、--force-recreate を指定してコンテナを再生成することもできます。

docker-compose up -d --force-recreate

コンテナを再起動させるには restart、コンテナを停止させるには stop を実行します。

docker-compose restart

docker-compose stop

コンテナをすべて削除するには rm を実行します。本当に削除するかどうかの確認を省略する場合、-f もしくは --force オプションを指定します。

docker-compose rm -f

docker-compose rm --force


ターミナルの対話モード

PHP のバージョンを調べてみましょう。

docker run php:7.0-cli php -v

対話モードから PHP のバージョンを調べてみましょう。

docker run -it php:7.0-cli /bin/bash

php -v
exit

ターミナルから HTTP サーバーを起動させて、ホストマシンからアクセスすることもできます。

docker run -it -p 80:80 php:7.0-cli /bin/bash

cd home
echo test > index.html
php -S 0.0.0.0:80

今度は intl エクステンションをインストールしたイメージを用意してみましょう。


Dockerfile

FROM php:7.0-cli

RUN apt-get update && apt-get install -y --no-install-recommends \
nano \
libicu-dev \
&& rm -rf /var/lib/apt/lists/*

RUN docker-php-ext-install intl


イメージをビルドします。

docker build -t masakielastic/php:7.0-cli ./

対話モードで入って、intl モジュールが入っていることを確認しましょう。

docker run -it php:7.0-cli /bin/bash

php -m | grep intl
exit


docker-compose で対話モード

docker-compose のサブコマンドの run で対話モードに入ることができます。PHP を試してみましょう。

> docker-compose run myapp

> php -v
> exit

myapp:

image: php:7.0-cli
entrypoint: /bin/bash


PHP ビルトインサーバー

最初の練習として PHP のビルトインサーバーのコンテナをつくってみましょう。


docker-compose.yml

app:

image: debian:jessie
volumes:
- ./www:/usr/share/php/html
tty: true
php:
image: php:7.0-cli
expose:
- "80"
ports:
- "80:80"
volumes_from:
- app
working_dir: /usr/share/php/html
command: php -S 0.0.0.0:80

docker-compose.yml と同じディレクトリに www フォルダーを用意し、HTML や PHP ファイルを設置します。すでに存在するイメージを使うので、docker-compose up -d を実行するだけで、コンテナが生成されます。

ターミナルからコマンドツールを使うことができます。

docker exec -it php_container_id php -v

コンテナに入って php コマンドを実行することもできます。

docker exec -it php_container_id bash

php -v


Apache HTTP サーバー

Apache HTTP Server は v2.4.17 で HTTP/2 (mod_http2) に対応しましたが、nghttp2 の公式パッケージ (0.6.7) が古いので、PPA (Ubuntu の場合、ppa:ondrej/apache2) を利用させてもらうかソースコードを自分でビルドする必要があります (2015年時点)。


mod_php

PHP 公式リポジトリのイメージを少し修正して、TLS/SSL を有効にしてみましょう。Debian の場合、ssl-cert パッケージをインストールすれば、自己証明書をつくる手間を省くことができます。

app:

image: debian:jessie
volumes:
- ./www:/var/www/html
tty: true
php:
build: ./
ports:
- "80:80"
- "443:443"
volumes_from:
- app


Dockerfile

FROM php:7.0-apache

RUN apt-get update && apt-get install -y ssl-cert \
--no-install-recommends && rm -r /var/lib/apt/lists/*

RUN a2enmod ssl
EXPOSE 443

COPY ssl-default.conf /etc/apache2/sites-enabled/



ssl-default.conf

<IfModule mod_ssl.c>

<VirtualHost _default_:443>
DocumentRoot /var/www/html

SSLEngine on
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
SSLCACertificatePath /etc/ssl/certs/
</VirtualHost>
</IfModule>



mod_proxy_fcgi (FastCGI)

Apache の公式リポジトリのイメージを使います。


docker-compose.yml

app:

image: debian:jessie
volumes:
- ./www:/usr/local/apache2/htdocs
tty: true
php:
image: php:7.0-fpm
ports:
- "9000:9000"
volumes_from:
- app
apache:
build: ./
ports:
- "80:80"
- "443:443"
volumes_from:
- app
links:
- php

httpd の build で指定したディレクトリに Dockerfile を設置します。今回の場合、docker-compose.yml と同じディレクトリになります。


Dockerfile

FROM httpd:2.4

ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update && apt-get install -y ssl-cert ca-certificates \
--no-install-recommends && rm -r /var/lib/apt/lists/*

EXPOSE 443

RUN sed -i '/LoadModule proxy_module/s/^#//g' /usr/local/apache2/conf/httpd.conf
RUN sed -i '/LoadModule proxy_fcgi_module/s/^#//g' /usr/local/apache2/conf/httpd.conf
RUN sed -i '/LoadModule ssl_module/s/^#//g' /usr/local/apache2/conf/httpd.conf
RUN sed -i '/LoadModule socache_shmcb_module/s/^#//g' /usr/local/apache2/conf/httpd.conf

COPY ssl-default.conf /usr/local/apache2/conf/httpd-ssl.conf

RUN { \
echo 'DirectoryIndex disabled'; \
echo 'DirectoryIndex index.php index.html'; \
echo '<FilesMatch \.php$>'; \
echo ' SetHandler "proxy:fcgi://php:9000"'; \
echo '</FilesMatch>'; \
echo 'Include conf/httpd-ssl.conf'; \
} >> /usr/local/apache2/conf/httpd.conf



ssl-default.conf

<IfModule mod_ssl.c>

Listen 443

<VirtualHost *:443>
DocumentRoot /usr/local/apache2/htdocs

SSLEngine on
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
SSLCACertificatePath /etc/ssl/certs/
</VirtualHost>
</IfModule>



nginx

nginx の設定ファイルを PHP 対応にするため、イメージをビルドします。


docker-compose.yml

app:

image: debian:jessie
volumes:
- ./www:/usr/share/nginx/html
tty: true
php:
image: php:7.0-fpm
ports:
- "9000:9000"
volumes_from:
- app
nginx:
build: ./
ports:
- "80:80"
- "443:443"
links:
- php
volumes_from:
- app

docker-compose.yml と同じディレクトリに nginx のための Dockerfiledefault.conf を設置します。


Dockerfile

FROM nginx:1.9

COPY default.conf /etc/nginx/conf.d/default.conf


default.conf

server {

listen 80;
listen 443 ssl http2;

server_name localhost;

ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;

# intermediate configuration. tweak to your needs.
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

root /usr/share/nginx/html;
index index.php;

location ~ \.php$ {
fastcgi_pass php:9000;
fastcgi_index index.php;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}


nginx 1.9.5 から HTTP/2 モジュールが利用できるようになっています。


h2o

2015年時点では h2o の公式リポジトリは存在しないので、自分でビルドする必要があります。


docker-compose.yml

app:

image: debian:jessie
volumes:
- ./www:/usr/share/h2o/html
tty: true
php:
image: php:7.0-fpm
expose:
- "9000"
volumes_from:
- app
h2o:
build: ./
ports:
- "80:80"
- "443:443"
volumes_from:
- app
links:
- php


Dockerfile

FROM debian:jessie

ENV DEBIAN_FRONTEND noninteractive
ENV H2O_VERSION 1.5.0

RUN apt-get update && apt-get install -y ca-certificates \
curl build-essential cmake openssl libssl-dev \
libyaml-dev --no-install-recommends \
&& rm -rf /var/lib/apt/lists/*

RUN curl -L "https://github.com/h2o/h2o/archive/v$H2O_VERSION.tar.gz" -o h2o.tar.gz
RUN tar xfz h2o.tar.gz
RUN mv "h2o-$H2O_VERSION" /usr/src/h2o
RUN rm -r h2o.tar.gz

WORKDIR /usr/src/h2o
RUN cmake . && make h2o
RUN mv h2o /usr/local/bin

COPY h2o.conf /usr/src/h2o/h2o.conf

RUN mkdir -p /usr/share/h2o/html
WORKDIR /usr/share/h2o/html

EXPOSE 80 443
CMD h2o -c /usr/src/h2o/h2o.conf



h2o.conf

file.custom-handler:

extension: .php
fastcgi.connect:
host: php
port: 9000
type: tcp
file.index:
["index.php", "index.html"]
hosts:
"localhost":
listen:
port: 443
ssl:
certificate-file: /usr/src/h2o/examples/h2o/server.crt
key-file: /usr/src/h2o/examples/h2o/server.key
paths:
"/":
file.dir: /usr/share/h2o/html
"localhost":
listen:
port: 80
paths:
"/":
file.dir: /usr/share/h2o/html


MySQL

MySQL 5.7.0 から MySQL 5.7.10 までのバージョンを導入する場合、パスワードの有効期限に注意する必要があります。くわしくは「MySQL 5.7.4で導入されたdefault_password_lifetimeがじわじわくる」をご参照ください。

デフォルトの設定で使う例を示します。


docker-compose.yml

app:

image: debian:jessie
volumes:
- ./www:/var/www/html
- /var/lib/mysql
tty: true
php:
build: ./
ports:
- "80:80"
links:
- mysql
volumes_from:
- app
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: mypass
volumes_from:
- app


Dockerfile

FROM php:7.0-apache

RUN docker-php-ext-install pdo_mysql mysqli

文字コードの設定ファイルを追加するのであれば、mysql の Dockerfile は次のようになります。

FROM mysql:5.7

RUN { \
echo '[mysqld]'; \
echo 'character-set-server = utf8mb4'; \
echo '[client]'; \
echo 'default-character-set = utf8mb4'; \
} > /etc/mysql/conf.d/charset.cnf

コマンドツールから日本語を入力できるようにするには、ロケールパッケージのインストールと設定が必要になります。

RUN apt-get update && apt-get install -y nano locales \

--no-install-recommends && rm -rf /var/lib/apt/lists/*

RUN dpkg-reconfigure locales && \
locale-gen C.UTF-8 && \
/usr/sbin/update-locale LANG=C.UTF-8

ENV LC_ALL C.UTF-8
ENV TERM xterm


MariaDB

MySQL とほとんど同じですが、コマンドツールを使うために、環境変数の TERM を設定する必要があります。


docker-compose.yml

app:

image: debian:jessie
volumes:
- ./www:/var/www/html
- /var/lib/mysql
tty: true
php:
build: ./
ports:
- "80:80"
links:
- mariadb
volumes_from:
- app
mariadb:
image: mariadb:10.0
environment:
MYSQL_ROOT_PASSWORD: mypass
TERM: xterm
volumes_from:
- app


Dockerfile

FROM php:7.0-apache

RUN docker-php-ext-install pdo_mysql mysqli


redis

PECL モジュールを使うことを前提とします。2015年9月時点では PHP 7 対応の正式リリースが存在しないため、php7 のブランチを使う必要があります。


docker-compose.yml

app:

image: debian:jessie
volumes:
- ./www:/var/www/html
tty: true
php:
build: ./
ports:
- "80:80"
volumes_from:
- app
links:
- redis
redis:
image: redis:3.0

セッションハンドラに redis を使うには、次のようになります。


Dockerfile

FROM php:7.0-apache

ENV DEBIAN_FRONTEND noninteractive
ENV PHPREDIS_VERSION php7

RUN curl -L -o /tmp/redis.tar.gz https://github.com/phpredis/phpredis/archive/$PHPREDIS_VERSION.tar.gz \
&& tar xfz /tmp/redis.tar.gz \
&& rm -r /tmp/redis.tar.gz \
&& mv phpredis-$PHPREDIS_VERSION /usr/src/php/ext/redis \
&& docker-php-ext-install redis

RUN { \
echo 'session.save_handler = redis'; \
echo 'session.save_path = tcp://redis:6379'; \
} >> /usr/local/etc/php/conf.d/docker-php-ext-redis.ini



memcached

2015年9月の時点では PHP 7.0 対応の公式リリースがないため、php7 のブランチを使う必要があります。


docker-compose.yml

app:

image: debian:jessie
volumes:
- ./www:/var/www/html
tty: true
php:
build: ./
ports:
- "80:80"
volumes_from:
- app
links:
- memcached
memcached:
image: memcached:1.4

セッションハンドラに memcached を使うには、次のようになります。


Dockerfile

FROM php:7.0-apache

ENV DEBIAN_FRONTEND noninteractive
ENV PHP_MEMCACHED_VERSION php7

RUN apt-get update && apt-get install -y libmemcached-dev zlib1g-dev

RUN curl -L -o /tmp/memcached.tar.gz https://github.com/php-memcached-dev/php-memcached/archive/$PHP_MEMCACHED_VERSION.tar.gz \
&& tar xfz /tmp/memcached.tar.gz \
&& rm -r /tmp/memcached.tar.gz \
&& mv php-memcached-$PHP_MEMCACHED_VERSION /usr/src/php/ext/memcached \
&& docker-php-ext-install memcached

RUN { \
echo 'session.save_handler = memcached'; \
echo 'session.save_path = memcached:11211'; \
} >> /usr/local/etc/php/conf.d/docker-php-ext-memcached.ini



その他


公式イメージの buildpack-deps を使う

Debian/Ubuntu の公式イメージを使う場合、ビルドツールやバージョン管理ツールがあらかじめインストールされた buildpack-deps を代わりに使う選択肢があります。


コンテナの再起動

OS を再起動させたときにコンテナも再起動させるには restart:always を指定します。


docker-compose.yml

restart: always



データボリュームコンテナの選択肢

連携させるコンテナと同じ OS を使うようにしています。異なる OS を使う場合、パーミッションとオーナーが異なるためにデータが正常に生成されなかったり、読み込みができないことがあります (Data-only container madness)。


PECL のインストール

シェルスクリプトの docker-php-ext-install が用意されています。

RUN docker-php-ext-install mbstring


Bash の日本語対応

RUN apt-get update && apt-get install -y nano locales \

--no-install-recommends && rm -rf /var/lib/apt/lists/*

RUN dpkg-reconfigure locales && \
locale-gen C.UTF-8 && \
/usr/sbin/update-locale LANG=C.UTF-8

ENV LC_ALL C.UTF-8
ENV TERM xterm


HTTP サーバーの SSL 設定

Apache や nginx の場合、Mozilla の SSL 設定ジェネレーターを使うとよいでしょう。Apache 2.4.10、OpenSSL 1.0.1k を対象に生成した設定ファイルは次のとおりです。


ssl-default.conf

<IfModule mod_ssl.c>

<VirtualHost _default_:443>
DocumentRoot /var/www/html

SSLEngine on
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
SSLCACertificatePath /etc/ssl/certs/

# intermediate configuration, tweak to your needs
SSLProtocol all -SSLv3
SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
SSLHonorCipherOrder on
SSLCompression off

# OCSP Stapling, only in httpd 2.3.3 and later
SSLUseStapling on
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off

# OCSP Stapling, only in httpd 2.3.3 and later
SSLUseStapling on
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off

# HSTS (mod_headers is required) (15768000 seconds = 6 months)
Header always set Strict-Transport-Security "max-age=15768000"
</VirtualHost>
</IfModule>

SSLStaplingCache shmcb:/var/run/ocsp(128000)


nginx の場合、次のようになります。


default.conf

server {

listen 443 ssl http2;

ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;

ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_prefer_server_ciphers on;

# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
add_header Strict-Transport-Security max-age=15768000;

# OCSP Stapling ---
# fetch OCSP records from URL in ssl_certificate and cache them
# ssl_stapling on;
# ssl_stapling_verify on;

## verify chain of trust of OCSP response using Root CA and Intermediate certs
# ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;

resolver 8.8.8.8;
}



ビルドしたイメージを Docker Hub にプッシュする

ログインした状態で docker push を実行します。

docker login

docker push masakielastic/h2o