Laravel5をDockerで動かす

Laravel5で構築したアプリケーションの開発環境をDocker化するための方法を紹介する.

サンプルアプリケーションはこちらで確認できる.

準備

以下の環境でLaravel5が動作するように, 環境を構築する.

  • OS
    • Ubuntu 14.04
  • ミドルウェア
    • Nginx
    • php-fpm 5.5
    • MySQL 5.5

Laravel5のインストール

まず, laravel5をインストールする. インストールにはcomposerを用いる. create-projectの時に, dev-developを指定するとインストールできる.

mkdir dockerized-laravel5
cd dockerized-laravel5
composer create-project laravel/laravel application dev-develop

PHPコンテナ

nginx+php-fpmな環境で, laravelが動作するようにDockerfileを記述する.

Dockerでは, 1コンテナ1プロセスにするのが好ましいが, 今回はnginxとphp-fpmを1コンテナで動かすために, supervisordをもちいる.

Dockerfileは以下のとおり.

FROM ubuntu:14.04

ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update

RUN apt-get install -yq --force-yes build-essential wget curl git ssh nginx nodejs-legacy npm mysql-client supervisor

RUN apt-get install -yq --force-yes  php5-cli php5 php5-fpm php5-mysql php5-curl php5-mcrypt php5-memcached && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

RUN php5enmod mcrypt

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

RUN mkdir -p /app
WORKDIR /app

ADD ./application/database /app/database
ADD ./application/tests /app/tests
ADD ./application/composer.json /app/composer.json
ADD ./application/composer.lock /app/composer.lock
RUN composer install --no-scripts

ADD ./application /app
RUN php artisan clear-compiled
RUN php artisan optimize

RUN usermod -u 1000 www-data
RUN groupmod -g 1000 www-data

RUN chown -R www-data:www-data /app

RUN echo "daemon off;" >> /etc/nginx/nginx.conf
ADD docker/nginx-site.conf /etc/nginx/sites-available/default

ADD docker/supervisord.conf /etc/supervisord.conf

EXPOSE 80

CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisord.conf"]

supervisord.conf

[unix_http_server]
file=/tmp/supervisor.sock

[supervisord]
logfile=/tmp/supervisord.log
pidfile=/tmp/supervisord.pid
nodaemon=false

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL  for a unix socket

[program:php5-fpm]
command=/usr/sbin/php5-fpm -c /etc/php5/fpm --nodaemonize

[program:nginx]
command=/usr/sbin/nginx

このDockerfileのポイントについて説明する.

composer.jsonを先にADDする

ADD ./application /appを行ったあとにRUN composer installとすると, composer.jsonの変更の有無にかかわらず, ./application内のどれかのファイルが変更されるごとに composer installが走る. そこで, 先にADD ./application/composer.json /app/composer.jsonRUN composer installを行って, その後に/applicationADDすることで, composer.jsonに変更がない場合は, その行がキャッシュされる. これは, RubyのBundlerで紹介されているテクニックをそのまま用いている.

ただし, composerの場合はそのままではうまくいかない. 問題は2つある.

  • composer.jsonautoloadに指定されたclassmapに対応するクラスは, composer install時に存在する必要がある.
  • composer install後に, composer.json内のscriptsに記述された処理が実行される.

この対処方法について述べる.

composerのautoloadに対応する

まず, composer.jsonのautoloadに対応する. といっても, composer install時にautoloadが必要とするクラスが存在するだけでOKなので, application内の, databasetestsディレクトリをADDすれば良い.

ADD ./application/database /app/database
ADD ./application/tests /app/tests

composerのscriptsに対応する

comopserは, 実行後に特定の処理を実行するためのフックが存在する. 具体的にはcomposer.json内のscriptsの項目の処理が実行される.

laravelでは, composer install後に, 以下の処理が実行される.

php artisan clear-compiled
php artisan optimize

artisanは, laravelのコマンドラインツールだが, これはlaravelのアプリケーションディレクトリが存在しないと実行できない. 上記のテクニックでは, composer install時には./application内のすべてのファイルが存在しないため, artisanが正常に実行できずにエラーとなる.

この問題は, composer install実行後に, scriptsに記述された処理を行わず, laravelのアプリケーションディレクトリを追加後に, scriptsの処理を行うことで対処できる.

具体的には, composer install--no-scripts付きで実行し, ./applicationをADDした後に, scriptsの項目の処理を実行する.

RUN composer install --no-scripts

ADD ./application /app
RUN php artisan clear-compiled
RUN php artisan optimize

.dockerignore

Dockerfileとは直接は関係ないが, Dockerコンテナに含める必要のないファイルやディレクトリは, .dockerignoreに記述することで, ADDやCOPYの対象から外れる.

.gitなどは省いておくのと, laravelアプリケーションディレクトリ内の, vendorcomposer.lockも対象から省いておかないと, Dockerコンテナ側でビルドした内容と, Dockerホスト側のディレクトリには差異が生じて, アプリケーションが動かない可能性がある.

MySQLコンテナ

この記事と同様に, Docker Hub公式のhttps://registry.hub.docker.com/_/mysql/を用いる.

dockerized-laravel5直下にfig.xmlを用意し, 以下のように記述する.

環境変数をenvironment:に設定することで, MySQLの設定を変更することができる.

また, my.cnfなどの設定を変更したい場合は, Dockerコンテナ内の/etc/mysqlに, ローカルのディレクトリをマウントすることで設定を置き換える方法もある(/path/to/mysql:/etc/mysqlをvolumesに追記する).

fig.yml

以上のDockerfileからPHPコンテナを立ち上げるためのFigの設定を示す. dockerized-laravel5ディレクトリ直下に, 以下の様な内容のfig.ymlを設置する.

db:
  image: mysql:5.5
  environment:
    - MYSQL_ROOT_PASSWORD=himitsunopassword
  ports:
    - "3306"
  volumes:
    - /var/lib/mysql
web:
  build: .
  ports:
    - "8080:80"
  volumes:
    - ./application:/app
  links:
    - db

開発用途で利用するために, volumes項目に, ./application:/appと記述する. こうすることで, ローカルでファイルを編集した内容が即座にDockerコンテナ内に反映される.

PHPコンテナとMySQLコンテナのリンクと設定

上記の設定では, Dockerのlink機能を用いて, PHPコンテナからMySQLコンテナ内のデータベースを利用できるようになっている.

アプリケーションからMySQLコンテナのDBに接続するための設定は, 環境変数を経由して取得できる.

環境変数の一覧は, 以下のように確認できる.

$ docker exec -it dockerizedlaravel5_web_1 env

DB_1_PORT_3306_TCP_ADDR=172.17.0.13
DB_1_PORT_3306_TCP_PORT=3306
DB_1_NAME=/dockerizedlaravel5_web_1/db_1
DB_1_ENV_MYSQL_ROOT_PASSWORD=himitsunopassword

laravelのデータベースの設定は, env関数を利用して行う.

    'mysql' => [
        'driver'    => 'mysql',
        'host'      => env('DB_1_PORT_3306_TCP_ADDR', 'localhost'),
        'database'  => env('DB_DATABASE', 'forge'),
        'username'  => env('DB_USERNAME', 'root'),
        'password'  => env('DB_1_ENV_MYSQL_ROOT_PASSWORD', ''),
        'charset'   => 'utf8',
        'collation' => 'utf8_unicode_ci',
        'prefix'    => '',
        'strict'    => false,
    ],

開発環境として利用する

figで管理しているコンテナの状態は, fig psで確認できる.

$ fig ps
          Name                        Command               State             Ports
--------------------------------------------------------------------------------------------
dockerizedlaravel5_db_1    /entrypoint.sh mysqld            Exit 0   0.0.0.0:49153->3306/tcp
dockerizedlaravel5_web_1   /usr/bin/supervisord -n -c ...   Exit 0

起動は, fig upで行う.

$ fig up
Recreating dockerizedlaravel5_db_1...
Recreating dockerizedlaravel5_web_1...
Attaching to dockerizedlaravel5_db_1, dockerizedlaravel5_web_1

起動中のコンテナでコマンドを実行するにはdocker execを利用する.

$ docker exec dockerizedlaravel5_web_1 ./artisan route:list
+--------+--------------------------------+-------------------------------------------------------+------+------------------------------------------------------------+------------+
| Domain | Method                         | URI                                                   | Name | Action                                                     | Middleware |
+--------+--------------------------------+-------------------------------------------------------+------+------------------------------------------------------------+------------+
|        | GET|HEAD                       | /                                                     |      | App\Http\Controllers\WelcomeController@index               |            |
|        | GET|HEAD                       | home                                                  |      | App\Http\Controllers\HomeController@index                  |            |
|        | GET|HEAD                       | auth/register/{one?}/{two?}/{three?}/{four?}/{five?}  |      | App\Http\Controllers\Auth\AuthController@getRegister       |            |
|        | POST                           | auth/register/{one?}/{two?}/{three?}/{four?}/{five?}  |      | App\Http\Controllers\Auth\AuthController@postRegister      |            |
|        | GET|HEAD                       | auth/login/{one?}/{two?}/{three?}/{four?}/{five?}     |      | App\Http\Controllers\Auth\AuthController@getLogin          |            |
|        | POST                           | auth/login/{one?}/{two?}/{three?}/{four?}/{five?}     |      | App\Http\Controllers\Auth\AuthController@postLogin         |            |
|        | GET|HEAD                       | auth/logout/{one?}/{two?}/{three?}/{four?}/{five?}    |      | App\Http\Controllers\Auth\AuthController@getLogout         |            |
|        | GET|HEAD|POST|PUT|PATCH|DELETE | auth/{_missing}                                       |      | App\Http\Controllers\Auth\AuthController@missingMethod     |            |
|        | GET|HEAD                       | password/email/{one?}/{two?}/{three?}/{four?}/{five?} |      | App\Http\Controllers\Auth\PasswordController@getEmail      |            |
|        | POST                           | password/email/{one?}/{two?}/{three?}/{four?}/{five?} |      | App\Http\Controllers\Auth\PasswordController@postEmail     |            |
|        | GET|HEAD                       | password/reset/{one?}/{two?}/{three?}/{four?}/{five?} |      | App\Http\Controllers\Auth\PasswordController@getReset      |            |
|        | POST                           | password/reset/{one?}/{two?}/{three?}/{four?}/{five?} |      | App\Http\Controllers\Auth\PasswordController@postReset     |            |
|        | GET|HEAD|POST|PUT|PATCH|DELETE | password/{_missing}                                   |      | App\Http\Controllers\Auth\PasswordController@missingMethod |            |
+--------+--------------------------------+-------------------------------------------------------+------+------------------------------------------------------------+------------+

また, コンテナ内にログインしたい場合は, docker execで, /bin/bashなどを実行する.

$ docker exec -it dockerizedlaravel5_web_1 /bin/bash
root@c489f074bb66:/app# ls
app  artisan  bootstrap  composer.json  composer.lock  config  database  gulpfile.js  package.json  phpspec.yml  phpunit.xml  public  readme.md  resources  storage  tests  vendor

まとめ

LaravelをDockerで動かすための方法を紹介した.

また このテクニックをcomposerに応用するためのテクニックをいくつか示した.

次に, 用意したDockerfileを用いて, テスト・CI・デプロイについて紹介していきたい.