Help us understand the problem. What is going on with this article?

docker-compose を使って(なるべく)公式イメージで PHP 開発環境を作った

More than 1 year has passed since last update.

正直PHP触りたくないんですが、仕事なので仕方ありませんでした…

この記事で得られるモノ

  • docker-compose で公式イメージを組み合わせていく雰囲気
  • docker-compose を用いた、デバッグの効く PHP 開発環境の立て方
  • docker-machine で使う Dockerfile を書く時の Tips など

「それ正直 Vagrant 挟んだほうが楽じゃない?」
「ぶっちゃけフルスタックのコンテナ作ってもよくない?」

ってところもありますが

  • Docker だけでやってみたかったから仕方ない!
  • docker-compose で小さなコンテナの組み合わせをやってみたかったから仕方ない!

仕方ない。

ホスト側の準備

  • VirtualBox
  • docker (docker-engine)
  • docker-machine
  • docker-compose

以上4点を用意します。GUI でよければ Docker Toolbox で一発フルセット。

CUI がお好みなら Windows は Chocolatey

Windows
@powershell -NoProfile -ExecutionPolicy Bypass -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin
choco install -y git docker docker-machine docker-compose virtualbox

OSX は brew で

OSX
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew tap caskroom/cask
brew update
brew install git docker docker-compose docker-machine
brew cask install virtualbox

docker-machine

Docker デーモンが動く中間マシンを生成します。名前は適当。

docker-machine create -d virtualbox phpdev

上の例では phpdev という VM ができるので、ちょろっと設定しておきます。
(Windows の場合は %VBOX_MSI_INSTALL_PATH% に Path を通しといてください)

# 設定変えたいので一旦止める
docker-machine stop phpdev

# 準仮想化インターフェースを KVM に変更し、ポートフォワーディングを3点追加
VBoxManage modifyvm phpdev \
  --paravirtprovider "kvm" \
  --memory "4096" \
  --natpf1 "http,tcp,,10080,,80" \
  --natpf1 "mysql,tcp,,3306,,3306" \
  --natpf1 "xdebug,tcp,,9000,,9000"
  • docker-machine が作る VM (boot2docker) は Tiny Core Linux なので KVM に設定。
  • メモリサイズはお好みで。
  • ポートフォワーディングは見ての通り
    • 1024 以下は割り当てられないので HTTP は 10080
    • ホスト, ゲストともに IP は指定しない(指定すると Xdebug でコンテナ→ホストを見たい時に不便)

ローカル開発で Web ページ叩くたびにポート10080を付けるのは面倒なので
localhost:10080 -> 80 にフォワード設定しときます。

Windows
netsh interface portproxy add v4tov4 listenport=10080 listenaddress=127.0.0.1 connectport=80 connectaddress=127.0.0.1

# 消すとき
# netsh interface portproxy delete v4tov4 listenport=10080 listenaddress=127.0.0.1
OSX
echo "rdr pass on lo0 inet proto tcp from any to 127.0.0.1 port 80 -> 127.0.0.1 port 10080" | sudo pfctl -ef -

# 消すとき
# sudo pfctl -df /etc/pf.conf

これからいろいろファイルを作り始めるので、プロジェクト的なディレクトリを作っておきましょう。
ついでにお好みで .editorconfig.gitignore も。

mkdir -p ~/phpdev
cd ~/phpdev

cat << _EOC_ > .editorconfig
root = true

[*]
end_of_line = lf
charset = utf-8
indent_size = 2
indent_style = space
_EOC_

curl -s https://www.gitignore.io/api/eclipse%2Cosx%2Cwindows > .gitignore

マシンの上げ下げをポートフォワーディングとまとめてスクリプトにしておきます。
(ポートフォワーディングのコマンドは OS ごとに適宜読み替えてください)

touch up.sh down.sh MACHINE
chmod +x up.sh down.sh

マシン名読み込み用

MACHINE
MACHINE=phpdev

上げ

up.sh
#!/bin/bash
cd $(dirname $0)

. ./MACHINE
docker-machine start $MACHINE
eval $(docker-machine env $MACHINE)

echo "rdr pass on lo0 inet proto tcp from any to 127.0.0.1 port 80 -> 127.0.0.1 port 10080" | sudo pfctl -ef -

下げ

down.sh
#!/bin/bash
cd $(dirname $0)

. ./MACHINE
docker-machine stop $MACHINE

sudo pfctl -df /etc/pf.conf

サービスを定義していく

なるべく公式イメージを使いつつ docker-compose.yml にサービス定義を書いていきます。

docker-compose.yml
version: "2"

services:

まだ空っぽです。

※以降 diff ハイライトで書いていきますが最後に全体も載せときます。

app

データボリュームコンテナとなるイメージ。

docker-compose.yml
  version: "2"

  services:
+    app:
+      image: "busybox:latest"
+      volumes:
+        - "/path/to/myapp:/app"

特に語ることナシ。

php

拡張なしで済むなんてことはまずありえないのでいきなり Dockerfile 書くハメになりますが、公式の手法に倣います。

php/Dockerfile
FROM php:5.5-fpm
MAINTAINER hidekuro

# ミラー変更
RUN curl -sL http://www.debian.or.jp/using/apt/sources.list.http.ftp.jp.debian.org > /etc/apt/sources.list \
  && apt-get update

# PHP拡張の有効化
RUN docker-php-ext-install -j$(nproc) \
    mbstring \
    pdo \
    pdo_mysql \
&& yes "" | pecl install xdebug \
&& docker-php-ext-enable xdebug

# 汎用ユーザー
RUN useradd -m -s /bin/bash -u 1000 developer

# ソケットを置く場所
RUN mkdir -p /var/run/php-fpm \
  && chown developer:developer /var/run/php-fpm

# 設定ファイルの設置
COPY php/php.ini /usr/local/etc/php/php.ini
COPY php/conf.d/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini
COPY php-fpm.d/www.conf /usr/local/etc/php-fpm.d/zzz-www.conf

VOLUME ["/var/run/php-fpm"]

公式イメージが提供するコマンドを使用して拡張を有効化します。
PECL 拡張の場合は Enter 要求されるので yes "" のパイプをお忘れなく。

拡張の種類によっては zlib1g-dev とか libxml2-dev とか必要になってきますので、
その場合は docker-php-ext-install より前にインストールするようにします。

拡張ごとに必要なライブラリは、 PHP マニュアルの要件ページ(例えば Zip ならここ )で確認できます。

マニュアル見てもよくわからない場合は、

docker run --rm -it php:5.5-fpm bash

…で一時的にコンテナを起動して、実際に RUN で叩こうとしてるコマンドを全部ぶっ放すのが手っ取り早いです。
その場で php 実行して確かめることも出来ますし。

閑話休題。 Dockerifle の中身に戻ります。

ユーザーの UID を 1000 にしているのは適当ではなく、 docker-machine VM の docker エージェントユーザーが uid=1000 になるため です。
このため、コンテナにボリュームをマウントすると所有者 UID 1000 になります。

ボリュームを定義したり、ホストディレクトリをマウントしてリソースを与えるようなイメージを書く場合、自作ユーザーも -u 1000 にしておくことを強くおすすめします。

ini はよくあるカスタム用で、公式イメージの構造では /usr/local/etc/php 以下に置く決まりになっています。

fpm の設定ファイルを zzz-www.conf としていますが、これは 公式イメージが zz-docker.conflisten 設定をぶっ潰しているのに対抗するためです。
後に読まれたものが勝つので。どう見ても暫定回避のような構造ですが仕方ありません。

書いた Dockerfile をビルドして使用するサービスを定義します。

docker-compose.yml
  version: "2"

  services:
    app:
      # ...
+   php:
+     build: "./php"
+     image: "myapp/php"
+     network_mode: "host"
+     ports:
+       - "9000:9000"
+     volumes:
+       - "/var/run/php-fpm"
+     volumes_from:
+       - "app"
+       - "db"

FPM は TCP/IP ではなく Unix ソケットを使うことにするので、 volumes でソケットを露出させるためのボリュームを定義してます。

また、実際に PHP プログラムが実行されるのはこのイメージからできたコンテナ上なので、
後で定義する DB のソケットやアプリのソースが見えるように volumes_from で対象サービス側で定義されているボリュームを参照します。

ポート 9000 は Xdebug 用です。

ネットワークをホスト OS (Win, OSX) から見て透過的にするために、 network_mode: "host" で Docker ホスト (phpdev) にブリッジさせます。

置いてる設定ファイルはこちら

php/conf.d/xdebug.ini
; see http://xdebug.org/docs/all_settings
xdebug.remote_enable=1
xdebug.remote_autostart=1
xdebug.remote_connect_back=1
xdebug.remote_port=9000

Xdebug セッションの経路は コンテナ⇔VM⇔ホストOS というリモート接続になるので remote_enable=1 がまず必要です。
remote_autostart, remote_connect_back によって、接続元を指定せずにリモートデバッグできるようにします。
ポート9000はデフォなのでいらないかも。

FPM の設定がこちら

php-fpm.d/www.conf
[www]
listen = /var/run/php-fpm/php-fpm.sock
listen.owner = developer
listen.group = developer
listen.mode = 0660
user = developer
group = developer

ボリュームとして露出させる /var/run/php-fpm にソケットを置き、自作ユーザーで動作させます。

nginx

モジュールを追加しない場合は公式イメージが使えますが、 docker-compose でやろうとすると
設定ファイルの設置が複雑(マウントして置換して…云々)になるので Dockerfile 書いたほうが楽だと思います。

docker-compose.yml
  version: "2"

  services:
    app:
      # ...
    php:
      # ...
+   web:
+     build: "./web"
+     image: "myapp/web"
+     network_mode: "host"
+     ports:
+       - "80:80"
+     volumes_from:
+       - "app"
+       - "php"
web/Dockerfile
FROM nginx:1.9
MAINTAINER hidekuro

RUN useradd -m -s /bin/bash -u 1000 developer

COPY web/nginx.conf /etc/nginx/nginx.conf

アプリのソースと php-fpm ソケットを見るために、各サービスのボリュームを見てます。

そして nginx.conf で

nginx.conf
user developer;

# ...

server {

    # ...

    location ~ \.php$ {
        root /app;
        fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    }
}

と言った感じで、 php サービスが提供する unix:/var/run/php-fpm/php-fpm.sock
app サービスが提供する /app を使うような記述ができます。

1行目でユーザー変えてます。例の汎用ユーザー。

mysql

これも基本的には公式イメージで足りますが、今回はソケットのパスを変えたかったのと
Windows のとある事情のためにやっぱり Dockerfile 書きます。

docker-compose.yml
  version: "2"

  services:
    app:
      # ...
    php:
      # ...
    web:
      # ...
+   db:
+     build: "./db"
+     image: "myapp/db"
+     network_mode: "host"
+     ports:
+       - "3306:3306"
+     volumes:
+       - "/var/lib/mysql"
+       - "/var/log/mysql"
+     environment:
+       MYSQL_ROOT_PASSWORD: "root"
db/Dockerfile
FROM mysql:5.6
MAINTAINER hidekuro

COPY conf.d /etc/mysql/conf.d

# Windows ホストのケア
RUN chmod -R 744 /etc/mysql

VOLUME ["/var/lib/mysql", "/var/log/mysql"]

どうやら Windows ホストで作成して COPY したファイルは 0666 になる らしく、
これをやっておかないと Warning: World-writable config file '/etc/mysql/my.cnf' is ignored と怒られてファイルを無視されます。

my.cnf はファイル単位で COPY してもいいですが、 公式イメージのページのやり方 にしたがって、ディレクトリ単位で置いています。

db/conf.d/my.cnf
[client]
socket = /var/lib/mysql/mysql.sock

[mysqld]
datadir = /var/lib/mysql
socket = /var/lib/mysql/mysql.sock
character-set-server = utf8
skip-character-set-client-handshake = 1
symbolic-links = 0

log_output = FILE
log_warnings = 1
general_log = 1
general_log_file = /var/log/mysql/general.log
log_queries_not_using_indexes = 1
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow_query.log
long_query_time = 0.5
expire_logs_days = 7

[mysqld_safe]
log-error = /var/log/mysql/error.log

ちょっと長いですが、開発用ということで最低限のログをとるようにしてます。
また、ソケットのパスとデータディレクトリを指定しています。

上げ下げシェルに追記

定義してきたサービス群をコンテナとしてまとめて起動するには、 docker-compose コマンドを使います。

上げ

up.sh
  #!/bin/bash
  cd $(dirname $0)

  . ./MACHINE
  docker-machine start $MACHINE
  eval $(docker-machine env $MACHINE)

+ docker-compose up -d

  echo "rdr pass on lo0 inet proto tcp from any to 127.0.0.1 port 80 -> 127.0.0.1 port 10080" | sudo pfctl -ef -

下げ

down.sh
  #!/bin/bash
  cd $(dirname $0)

  . ./MACHINE
+ eval $(docker-machine env $MACHINE)
+ docker-compose down

  docker-machine stop $MACHINE

  sudo pfctl -df /etc/pf.conf

まとめソース

https://github.com/hidekuro/qiita-sample-phpdev


以上です。

結局ほとんど Dockerfile 書いとる…

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした