docker-compose で apache2+PHP 開発環境 (silex, CLI, mysql, redis)

docker-compose でなるべく楽をして PHP 開発環境を作成する流れについて、一応使えそうになったのでまとめます。

ローカルに PHP を入れたりせず、Docker さえあればアプリケーションの CLI や composer を使って開発ができることを目的にします。

想定する PHP アプリ

  • Silex
  • MySQL、Redis
  • apache2 + PHP7.1

作業環境

Windows, Mac どちらでも Docker が動けば問題ないはずです。いちおう下記で基本動作を確認。

開発環境

ディレクトリ構成

基本的には Silex Skeleton に従います。

├── bin
│   └── console
├── composer.json
├── composer.lock
├── config
├── docker
│   └── app
│        ├── app.conf
│        ├── Dockerfile
│        ├── mpm_prefork.conf
│        └── php.ini
├── docker-compose.yaml
├── etc
│   ├── mysql.php
│   └── redis.php
├── README.md
├── src
├── templates
├── tests
├── var
└── web

アプリのルートに docker-compose.yaml を、docker/app 以下にアプリケーションコンテナ関連のファイルを置いています。

composer install

composer install します。composer 本体はDocker公式レポジトリのイメージ をバイナリのように使えます。

アプリのルートで以下のコマンドを実行します。

$ docker run --rm --interactive --tty --volume ${PWD}:/app composer install

無事 vendor ディレクトリが生成されるはずです。

msys2 など cygwin 互換環境の場合は --volume `cygpath $PWD -w`:/app とするとパスが解決できるかと思います。(以下 volume オプション使用時同様)

主題とは関係ないですが、日本で使う場合には composer.json にミラーの設定をしておくと速度が上がって多少苦しみが緩和されると思います。

...
"repositories": {
    "packagist": {
         "type": "composer",
         "url": "https://packagist.jp"
    }
},
...

アプリケーションコンテナ

apache2 + PHP が動くコンテナをビルドします。 手軽さを優先し、Docker公式レポジトリのPHPイメージ を使います。

docker/app/Dockerfile を書きます。今回は MySQL, Redis を使いたいので extension を追加します。

docker/app/Dockerfile

FROM php:7.1-apache

RUN docker-php-ext-install -j$(nproc) mbstring opcache pdo pdo_mysql

RUN apt-get update && apt-get install -y zlib1g-dev \
 && pecl install redis-3.1.3 \
 && docker-php-ext-enable redis \
 && apt-get autoremove --purge -qq \
 && apt-get clean \
 && rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/*

COPY php.ini /usr/local/etc/php/
COPY app.conf /etc/apache2/sites-available/app.conf
RUN a2dissite '*default*' \
 && a2ensite app \
 && service apache2 restart

公式イメージのページにも十分な説明があるとは言えないのですが、docker-php-ext-install だけですんなり入るものとそうでないものがあります。phpredis はビルドの必要がありました。他にも、例えば CURLdocker-php-ext-install の候補に含まれているものの、事前に libcurl3-dev が必要だったりするので、目的の物によっては GitHub issues を探す必要はあるかもしれません。

ここでは、MySQL、Redis 関連の extension 追加の他に、php.ini と apache の設定をイメージに追加しています。

apache2 には後述の docker-compose で調整するマウントポイントが DocumentRoot になるように設定しておきます。この辺はお好みです。

docker/app/app.conf

<VirtualHost *:80>
    DocumentRoot /app/web

    <Directory /app/web>
        Options all
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

php.ini には、開発用に

docker/app/php.ini

log_errors = On
error_log = /dev/stderr

を記述しておくことで、PHP ログを docker 標準のログとしてとり扱うことができます。

MySQL、Redis コンテナ

今回は公式のイメージそのままで使います。docker-compose.yaml の記述のみですむので専用の Dockerfile は用意しません。

docker-compose で環境立ち上げ

docker-compose でアプリケーション、MySQL、Redis を立ち上げます。

docker-compose.yaml

version: '3'
services:
  app:
    build:
      context: ./docker/app
    volumes:
      - .:/app
    working_dir: /app
    ports:
      - "3000:80"
    environment:
      - SYMFONY_ENV=dev
      - DB_MASTER_HOST=mysql
      - DB_MASTER_USER=app
      - DB_MASTER_PASSWORD=app
      - DB_REPLICA_HOST=mysql
      - DB_REPLICA_USER=app
      - DB_REPLICA_PASSWORD=app
    depends_on:
      - mysql
      - redis

  mysql:
    image: mysql:5.7
    command: "mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci"
    volumes:
      - "mysql-myapp-data:/var/lib/mysql"
    environment:
      - "MYSQL_DATABASE=myapp_dev"
      - "MYSQL_ROOT_PASSWORD=root"
      - "MYSQL_USER=app"
      - "MYSQL_PASSWORD=app"
    ports:
      - "3306:3306"

  redis:
    image: redis

volumes:
  mysql-myapp-data:

app はアプリケーションを動かす apache2+PHP コンテナです。docker/app 以下をビルドし、カレント(アプリのルート)ディレクトリを /app にマウントしています。また、後々コマンドラインが使いやすいように working_dir を設定しています。環境変数では主に MySQL 関連の情報を渡しています。

以前の compose file v1 の頃からの差分になりますが、v3 では link を書かなくてもデフォルトで他のコンテナを名前解決できるようになっています。depends_on は起動順番を指定するものですが、MySQL コンテナは起動後準備できるまでにラグがあり、これを書いたからといって app 起動前に他の環境が全て ready というわけでもなくちょっと微妙です。

mysql では永続化ボリュームを設定しています。

ここまでアプリの動作環境がそろいました。

$ docker-compose up

で初回のアプリケーションコンテナのビルド、MySQL、Redis コンテナの Pull が行われるはずです。

立ち上がったら http://localhost:3000 でアプリケーションにアクセスできます。

CLI を使う

Silex Skeleton では bin/consoleCLI が使えるようになっています。他のフレームワークでも Railsbin/ のように CLI ツールを置くことがあるでしょう。

今回のようにローカルに実行言語を入れない場合でも、app コンテナを使って CLI を叩くことができます。

$ docker-compose exec app bin/console -h
Usage:
  list [options] [--] [<namespace>]

Arguments:
  namespace            The namespace name

Options:
...

今回はお試し CLI として、doctrine/migrations で DB のマイグレーションをやってみます。doctorin 自体の使い方はこちらの Silexだってdoctorin/migrations使う を参考にさせていただきました。

├── bin
│   └── console
├── composer.json
├── composer.lock
├── config
├── db
│  ├── migrations
│  ├── migrations.yml
│  └── migrations-db.php
...
├── src
│  ├── console.php

db/ 以下に関連ファイルを置きました。

db/migrations.yml

name: Doctrine Migrations
migrations_namespace: MyApp\Db\Migrations
table_name: doctrine_migration_versions
migrations_directory: ./db/migrations

db/migrations-db.php

<?php

return [
    'driver'    => 'pdo_mysql',
    'host'      => getenv('DB_MASTER_HOST'),
    'dbname'    => 'myapp_' . getenv('SYMFONY_ENV'),
    'user'      => getenv('DB_MASTER_USER'),
    'password'  => getenv('DB_MASTER_PASSWORD'),
    'charset'   => 'utf8mb4',
];

参考記事のように、console.php に migration コマンドを登録します。

use Doctrine\DBAL\Migrations\Tools\Console\Command\DiffCommand;
use Doctrine\DBAL\Migrations\Tools\Console\Command\ExecuteCommand;
use Doctrine\DBAL\Migrations\Tools\Console\Command\GenerateCommand;
use Doctrine\DBAL\Migrations\Tools\Console\Command\LatestCommand;
use Doctrine\DBAL\Migrations\Tools\Console\Command\MigrateCommand;
use Doctrine\DBAL\Migrations\Tools\Console\Command\StatusCommand;
use Doctrine\DBAL\Migrations\Tools\Console\Command\VersionCommand;

$console = new Application('My Silex Application', 'n/a');
...

/** Doctrine\DBAL\Migrations */
$console->add(new DiffCommand());
$console->add(new ExecuteCommand());
$console->add(new GenerateCommand());
$console->add(new LatestCommand());
$console->add(new MigrateCommand());
$console->add(new StatusCommand());
$console->add(new VersionCommand());

準備できたら、migration 定義を生成、

$ docker-compose exec app bin/console migrations:generate --configuration db/migrations.yml --db-configuration db/migrations-db.php

db/migrations 以下に生成されたファイルを編集し、migration 実行します。

$ docker-compose exec app bin/console migrations:migrate --configuration db/migrations.yml --db-configuration db/migrations-db.php

array(5) {
  ["host"]=>
  string(5) "mysql"
  ["dbname"]=>
  string(9) "myapp_dev"
  ["user"]=>
  string(3) "app"
  ["password"]=>
  string(3) "app"
  ["charset"]=>
  string(7) "utf8mb4"
}
Loading configuration from command option: db/migrations.yml

                    Doctrine Migrations


WARNING! You are about to execute a database migration that could result in schema changes and data lost. Are you sure you wish to continue? (y/n)y
Migrating up to 2017xxxxxxxxxx from 0

  ++ migrating 2017xxxxxxxxxx

     -> CREATE TABLE example (col1 LONGTEXT NOT NULL) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB

  ++ migrated (0.33s)

  ------------------------

  ++ finished in 0.33s
  ++ 1 migrations executed
  ++ 1 sql queries

(migration の中身は適当です) 無事コマンド実行できました。

まとめ

docker-compose でローカル環境の影響を最小限に開発環境が整いました。

引き続き、lint ツールの統合やデプロイについてもまとめる予定です。

特に参考にさせていただいた記事