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 エクステンションをインストールしたイメージを用意してみましょう。
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 のビルトインサーバーのコンテナをつくってみましょう。
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
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/
<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 の公式リポジトリのイメージを使います。
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
と同じディレクトリになります。
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
<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 対応にするため、イメージをビルドします。
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 のための Dockerfile
と default.conf
を設置します。
FROM nginx:1.9
COPY default.conf /etc/nginx/conf.d/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 の公式リポジトリは存在しないので、自分でビルドする必要があります。
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
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
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がじわじわくる」をご参照ください。
デフォルトの設定で使う例を示します。
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
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 を設定する必要があります。
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
FROM php:7.0-apache
RUN docker-php-ext-install pdo_mysql mysqli
redis
PECL モジュールを使うことを前提とします。2015年9月時点では PHP 7 対応の正式リリースが存在しないため、php7 のブランチを使う必要があります。
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 を使うには、次のようになります。
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 のブランチを使う必要があります。
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 を使うには、次のようになります。
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
を指定します。
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 を対象に生成した設定ファイルは次のとおりです。
<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 の場合、次のようになります。
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