Docker で Laravel の環境構築をしました

やりたい事

  • Laravel の Eloquent の機能を色々試したい
  • 単体テスト形式でコードを書いて試したい
  • テスト実行時には、開発用 DB とは別にテスト用の DB を用意したい

Eloquent を勉強するため、まずは検証環境を用意する事から着手しました。 その時の過程やハマったポイント等をまとめました。

環境情報

  • Docker version 18.09.2
  • docker-compose version 1.23.2

アプリ(Laravel)の環境構築

こちらの記事を参考にさせて頂きました。構成はほぼ同じです。

ただ、PHP のバージョンは 7.3 で作成したかったため、一部変更して実行しました。

まずはアプリのみを構築(Laravel をインストールするまで)

  • docker-compose.yml
version: '3'

services:
  php:
    container_name: php
    build: ./docker/php
    volumes:
      - ./server:/var/www

  nginx:
    image: nginx
    container_name: nginx
    ports:
      - 80:80
    volumes:
      - ./server:/var/www
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - php

# db に関するものはいったん削除しています。(後で追加)

docker/php/Dockerfile を変更

PHP 7.3 を使いため、イメージ指定を変更しました。

また、apt-get でインストールするものにlibzip-devを追加しました。(ハマったポイントその1

  • docker/php/Dockerfile
FROM php:7.3-fpm # ← 7.2-fpm から変更
COPY php.ini /usr/local/etc/php/

RUN apt-get update \
    # ↓ libzip-dev を追加
    && apt-get install -y zlib1g-dev libzip-dev mysql-client \
    && docker-php-ext-install zip pdo_mysql

# Composer install
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
RUN php -r "if (hash_file('sha384', 'composer-setup.php') === '48e3236262b34d30969dca3c37281b3b4bbe3221bda826ac6a9a62d6444cdb0dcd0615698a5cbe587c3f0fe57a54d8f5') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
RUN php composer-setup.php
RUN php -r "unlink('composer-setup.php');"
RUN mv composer.phar /usr/local/bin/composer

ENV COMPOSER_ALLOW_SUPERUSER 1

ENV COMPOSER_HOME /composer

ENV PATH $PATH:/composer/vendor/bin

WORKDIR /var/www

RUN composer global require "laravel/installer"

後は、参考サイト通りに Laravel をインストールし、動作確認(Laravel と書かれたページが表示)まで実施します。

※ 次に行く前にいったん環境を停止しておきます。

$ docker-compose down

DB の環境構築

開発用 DB とは別にテスト用 DB を作成するため、また、テスト実行時にはちゃんとテスト用 DB へ向ける必要があるため、以下のサイトを参考にして進めました。

以下のルールで構築します。

  • 開発用 DB 名は study
  • テスト用 DB 名 study_testing

DB コンテナの定義を追記

MySQL は root ユーザーで運用したいため、MYSQL_USER,MYSQL_PASSWORDを削除しました。(※ 他ユーザーへの権限付与等の手間を省きました。手を抜きました。。。)

  • docker-compose.yml
version: '3'

services:
  php:
    〜 略 ~

  nginx:
    〜 略 〜

  db:
    image: mysql:5.7
    container_name: db-host
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: study # DB 名を変更
      # MYSQL_USER, MYSQL_PASSWORD は削除
      TZ: 'Asia/Tokyo'
    # ↓ utf8mb4_general_ci に変更
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
    volumes:
      - ./docker/db/data:/var/lib/mysql
      - ./docker/db/my.conf:/etc/mysql/conf.d/my.cnf
      - ./docker/db/sql:/docker-entrypoint-initdb.d
    ports:
      - 3306:3306

server/config/database.php を変更

テスト用 DB への接続情報を追記します。

  • server/config/database.php
<?php

    'connections' => [

        'sqlite' => [
            'driver' => 'sqlite',
            'database' => env('DB_DATABASE', database_path('database.sqlite')),
            'prefix' => '',
            'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
        ],

        'mysql' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_general_ci', // ← utf8mb4_general_ci に変更
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => true,
            'engine' => null,
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
        ],

        // ↑ の 'mysql' をキーとするものをコピーして、キーを変更します。 
        'mysql_testing' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_general_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => true,
            'engine' => null,
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
        ],

server/.env を変更

DB_HOST が 127.0.0.1 だったので、DB のコンテナ名を指定します。(※ ハマったポイントその2

  • server/.env
〜略〜
DB_CONNECTION=mysql
DB_HOST=db-host # DB コンテナの名前を指定
DB_PORT=3306
DB_DATABASE=study
DB_USERNAME=root
DB_PASSWORD=root
〜略〜

server/.env.testing ファイルを作成

server/.env ファイルをコピーして作成します。テスト時はstudy_testingを見にいくようにします。

  • server/.env.testing
〜略〜
DB_CONNECTION=mysql_testing # ← テスト用の方を見るように指定します。
DB_HOST=db-host
DB_PORT=3306
DB_DATABASE=study_testing # ← テスト用 DB を見るように指定します。
DB_USERNAME=root
DB_PASSWORD=root
〜略〜

テスト用 DB 作成 SQL を用意(※ハマったポイントその3

コンテナ起動時にテスト用 DB が作成されるように SQL ファイルを以下の場所に作成します。

CREATE DATABASE IF NOT EXISTS study_testing CHARACTER SET utf8mb4;

コンテナを起動する

$ docker-compose up -d

これで、ローカルから MySQL Workbench 等を使って DB コンテナへアクセスすれば、studystudy_testing という DB が作成されている事が確認出来るかと思います。

マイグレーション

まだ各 DB の中身は空っぽなので、Laravel のマイグレーションを利用してテーブルを作ります。

アプリ(Laravel)のコンテナに入る

以下のコマンドでコンテナに入ります。

$ docker-compose exec php bash

開発用 DB に対してマイグレーション

$ php artisan migrate

これで DB study 内に users テーブル等が作成されます。

テスト用 DB に対してマイグレーション

--env=testing を指定する事で、server/.env.testing ファイルを読み込んで実行する事が出来ます。

$ php artisan migrate --env=testing

これで、開発用 DB と同様に、study_testing にもテーブルが作成されます。

以上で、おおかた環境を整える事が出来ました。

Eloquent 10 本ノックの本編は次回に書きたいと思います!

ハマった事

ハマったポイントその1

  • $ docker-compose up -d 実行時にエラー

最初、libzip-dev は追記せずにイメージだけを変更して、

$ docker-compose up -d

を実行したところ、以下のようなエラーが出ました。

〜略〜
checking for pkg-config... /usr/bin/pkg-config
checking for libzip... not found
configure: error: Please reinstall the libzip distribution
ERROR: Service 'php' failed to build: The command '/bin/sh -c apt-get update     && apt-get install -y zlib1g-dev mysql-client     && docker-php-ext-install zip pdo_mysql' returned a non-zero code: 1

PHP 7.3 では、このlibzip-devが必要なようですが、そのソースまでは辿り着けませんでした。。

ハマったポイントその2

  • $ php artisan migrateSQLSTATE[HY000] [2002] Connection refused

エラー内容の詳細

$ php artisan migrate

   Illuminate\Database\QueryException  : SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_schema.tables where table_schema = database and table_name = migrations)

  at /var/www/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664
    660|         // If an exception occurs when attempting to run a query, we'll format the error
    661|         // message to include the bindings with SQL, which will make this exception a
    662|         // lot more helpful to the developer instead of just the database's errors.
    663|         catch (Exception $e) {
  > 664|             throw new QueryException(
    665|                 $query, $this->prepareBindings($bindings), $e
    666|             );
    667|         }
    668| 

  Exception trace:

  1   PDOException::("SQLSTATE[HY000] [2002] Connection refused")
      /var/www/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  2   PDO::__construct("mysql:host=127.0.0.1;port=3306;dbname=database", "root", "root", [])
      /var/www/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  Please use the argument -v to see more details.

DB にアクセス出来ないよ、というエラーが発生しました。

この時、ローカルの MySQL Workbench からは接続が出来て、空っぽのstudyデータベースが見れる事を確認しました。 そのため、php コンテナから db-host コンテナへのアクセスする設定が何かしら足りないんじゃないかと疑いました。

そして、server/.env ファイルを見ると、

DB_HOST=127.0.0.1

となっていたので、これを DB のコンテナ名 db-host にする事で解決出来ました。

ハマったポイントその3

  • ./docker/db/sql に置いた SQL ファイルが実行されない問題

テスト用の DB を用意する前に Laravel の環境を構築していたわけですが、その際にはもちろん DB が構築されており、 docker/db/data にデータが作成されている状態でした。

以下の参考サイトにもあるように、データが永続化されている場合には、初めての起動時にしか ./docker/db/sql に置いた SQL は実行されないようです。

開発段階では、データを永続化したいですし、

今更ではありますが、開発用 DB とテスト用 DB を分けて考える場合、同一コンテナではなくて別々のコンテナを用意してあげればよかったのかもしれません。。

まとめ

  • やはり環境構築は骨が折れます。。(慣れるしかないかな!?)
  • Docker 使ってるおかげでいくらでも作って、消してが試せる!(ローカルが汚れる心配なし!)
  • 次回は、いよいよ Eloquent を触って行きます!