Docker Toolboxを使ってDockerでLaravel開発環境を作ってみる

## はじめに
今までVagrant+VirtualBox、XAMPP、Laravel Homesteadと開発環境を構築してきましたが、現在本格的にエンジニア志望の身の上であるということと、友人がDockerで環境構築をしたという話を聞いたので私もこれからの開発環境をDockerに絞ってやっていこうと思い今回挑戦してみましたので備忘録として書きます。

一応Githubにも慣れる目的でpushしてありますが、基本的には後述の参考記事の手順通りにやったことをWindows10 Homeでやるために私がしたことを追記して書いたようになりますので、作ったDocker環境は同じです。

https://github.com/shitikamiyako/Docker-Laravel-Demo

## 最初に行っておきたいこと
Dockerを使うならMacかWindowsならせめてWindows10 Proを使うことをおすすめします、Homeだとまーややこしいというより、Dockerを使うメリットの半分くらいなくなっている気がしなくもないです。
Linuxに明るい人だとLinuxでやる人もいるそうな。

## 構築する環境
windows10 home（Ryzen 5 3600)
docker v18.06.1-ce(途中でv19.03.1にアップデートします)
VirtualBox 6.0.16

## Dockerとは？

私が語るよりも詳しいことは下記の記事をご覧になってください。

[さくらナレッジ様より Docker入門（第一回）～Dockerとは何か、何が良いのか～](https://knowledge.sakura.ad.jp/13265/)

[Dockerでプログラマが最低限知るべきことが、最速でわかるチュートリアル](https://qiita.com/woz/items/43462398765f8036f3a8#docker%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%81%A8%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%81%AE%E5%89%8A%E9%99%A4)

さくらナレッジさんの記事より引用させていただくと、Dockerとは簡潔に言うと以下のような仕組みです。

>Dockerは、Linuxのコンテナ技術を使ったもので、よく仮想マシンと比較されます。VirtualBoxなどの仮想マシンでは、ホストマシン上でハイパーバイザを利用しゲストOSを動かし、その上でミドルウェアなどを動かします。それに対し、コンテナはホストマシンのカーネルを利用し、プロセスやユーザなどを隔離することで、あたかも別のマシンが動いているかのように動かすことができます。そのため、軽量で高速に起動、停止などが可能です。

イメージとコンテナについて十全に理解するにはLinux及びDockerについてもう少し深く勉強する必要があるので、あくまで今の私の感覚で話をすると、イメージはコンテナを作る材料のようなものだと思うとわかりやすいかもしれません。
例えば、今回はLaravelをDockerに導入するために

Nginx(サーバー) 、PHP(言語)、 MySQL(データベース)

の3つを用意しないといけないのですけど、Dockerの場合これを用意するにはまず各々のイメージをDocker Hubのレポジトリから引っ張ってこないといけない(pullするという)ので、まずそれを行います。
そしてそれを使用するために用意してきたイメージから各コンテナを作ることになります。
こうすることで各コンテナがサーバー・PHP実行環境・データベースの役割を持つのであとはPHP実行環境にLaravelをインストールしてしまえばLaravel環境の完成ということになります。
よく言われるDockerの利点の1つがここで、開発環境によって使うイメージを組み合わせることで容易に異なる環境を構築することができます。
例えば上記の例においてもこれとは別にデータベースをMySQLではなくMariaDBにしたLaravel環境を用意したいということであれば、MariaDBイメージを用意すればいいということになりますね。

## Dockerインストール
DockerはMacを使ってると話が早いみたいなのですが、そうでなければ早くも詰まるポイントその1です。

通常、Macの場合であればDocker for Macと呼ばれるインストーラーからインストールできるのですがWindowsの場合だとかなりややこしいです。

まず、使用しているWindowsがWindows10 Proの64bit版であるかどうかで分岐します。
そうであるなら、Docker for Windowsというインストーラーが使えるのでこれを使いましょう。
違う場合、つまり今回のパターンだと残念ながらそのままだとDockerは使えずDocker Toolboxというものを使い、VirtualBox下でDockerを使うことになります。
仮想マシンを使わないのがDockerの利点の1つのはずなので虚無の気持ちを覚えますが、受け入れましょう。

まずGithubから最新版をダウンロードします。
[DockerToolbox](https://github.com/docker/toolbox/releases)

しかし、ここでも詰まるポイントがあってCPUがAMD製つまりRyzenを使用されてる場合はなんとバージョンによってインストールの際弾かれてしまう場合があります。
私はRyzenを使っているので見事に弾かれました。
その場合は、バージョンを下げたもので試してみてください、私は冒頭のバージョンで成功した後、最新版にアップグレードすることで事なきことを得ました。
さて、ToolboxをインストールするとDocker QuickStart Terminalというものがあるはずなのでそれを起動してください。
インストールの際にVirtual BoxやGitなどWindows10 HomeでDockerを使うために必要なものを同時にインストールするかどうか聞かれますが、各々で必要なければチェックを外しましょう。

無事にこのようにクジラが出てきたらDockerのインストール完了です。
以後Dockerを起動する際もDocker QuickStart Terminalから起動できます。
<img width="716" alt="2020-02-25_02h50_00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428779/1a7c85eb-5bff-5267-4911-27c585ddcca8.png">

あと、これも詰まるポイントの1つなのですが仮想環境を使うためには
Hyper-vの機能を有効にしないといけません。
これはCPUがIntel製かAMD製か、さらにWindowsがProかHomeかで、もっというとマザーボードがどこのものかで有効にする手段が違うのですが、このあたりはお使いの環境を調べてHyper-v 有効というキーワードでググっていただければ。
私の場合は、BIOSの設定から有効にしないといけませんでした。

## 開発環境の構築をしていく

[docker-composeでLaravelの開発環境を整える方法とその解説](https://www.membersedge.co.jp/blog/laravel-development-environment-with-docker-compose/)
(以下参考記事1と呼称させて頂きます。)

[Laravelの開発環境をDockerを使って構築する](https://qiita.com/ucan-lab/items/17c806973e69792ada99#laravel%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E6%96%B0%E8%A6%8F%E3%81%AB%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B%E5%A0%B4%E5%90%88)

詳しいことは上記の記事を参照して頂いた方がわかりやすく話も早いのですが、今回の場合はWindows10 Homeでの導入になりますので、私からも順を追って話をさせていただきます。

#### 1.Virtual Boxのポートフォワーディングの確認
まずは、Windows10 Homeの場合必ず確認しなればならないのがここです。
これ、今冷静に考えればWindows10 HomeでのDockerの使用はVirtual Boxをかませてその中の仮想マシン(Linux)で動かすわけですから、当然仮想マシン側のポートもちゃんと設定してあげないとlocalhostすらままならないんですけど、なぜか私はそれに気づかず8時間くらい溶かしてました。
こんな人は他にはいないと思いますけど、後々あれ？ とならないようにまずこちらから設定しましょう。

Virtual Boxを起動して、Dockerで使用している仮想マシンを選択して設定のところをクリックします。
どれを使用しているんだ？ となる方はDockerを起動して`docker-machine ls`とコマンドプロンプトで打つと、起動している仮想マシンの詳細が出るのでそこのNAMEの部分がDockerで使用されている仮想マシンの名前になりますので確認してみてください。
導入直後のままならおそらくdefaultという名前のはずです。

さて、設定をクリックしたあとはネットワークの項目を選択、高度と書かれているところのプルダウンメニューを表示させると下記のようにポートフォワーディングの項目が出るのでそこをクリックしましょう。
<img width="605" alt="2020-02-26_02h38_29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428779/d46616ae-a103-0dcc-0d5b-31e855e648f3.png">

すると下記のような画面が出てくるので、右側のプラスマークのアイコンからポートの設定ができます。

<img width="650" alt="2020-02-26_02h39_03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428779/3d099751-8346-0cf3-e003-a844346e1315.png">


名前・ホストIP(ローカル環境なので、127.0.0.1)、ホストポート、ゲストポートを設定します。
このあと、Dockerの方でもホスト⇒ゲストとポートの設定のをするのですが、ややこしいことにWindows10 HomeでのDocker利用は

**Windows⇒Virtual Box⇒Docker**

つまり

**大本のホスト⇒Dockerから見たホスト(Windowsから見るとゲスト)⇒ゲスト**

といったような構造になっているため、Docker側でVirtual Box⇒Dockerでのポートの設定をしても、Windows⇒Virtual Box側でポートを通してやらないと暖簾に腕押しということになってしまうのです。
ネットワークの知識に少し明るいとこういうところは当然なのかもしれないですが、私みたいなまだまだビギナーという人には落とし穴になりがちだと思うので最初に確認して頂きたいです。

#### 2.docker-compose.ymlの記述

参考記事1から引用させていただくと

>docker-compose.ymlには、yaml形式で、複数のコンテナで構成されるサービスを構築・実行する手順等を記述します。
それぞれのコンテナについて、イメージ作成時にベースにするDockerイメージやDockerfileの指定・ホスト側からDockerコンテナ側へのポートフォワーディングの指定・Dockerコンテナに共有する（ホスト側の）ファイル群の指定などを定義することができます。

ということになります。
つまり、このコンテナ(役割)を使った環境を作りますよという設計図のようなものですね。
なお、今回私は以下のようなディレクトリ構造で構築しましたので以後の説明はこれに沿っているものと考えてください。

**・docker-compose-laravel(ルートフォルダ)
├── demo-docker-app(Laravel)
├── docker
│   └── web
│       └── default.conf
│　 └── php
│       └── Dockerfile
├── docker-compose.yml
└── index.html
└── index.php**

ディレクトリの構築が終わったらdocker-compose.ymlに以下のように記述します。

```yml
version: '3'
services:
  web:
    image: nginx:1.15.6
    ports:
      - '8000:80'
    volumes:
      - ./docker/web/default.conf:/etc/nginx/conf.d/default.conf
      - .:/var/www/html
```

上記はNginxを実行するコンテナを定義しています。
versionは``docker-compose.yml``のファイルフォーマットのバージョン指定。

serviceはコンテナの名前の定義で、インテンドが1つ下がった部分が実行するコンテナの定義となります。
なので、``docker-compose.yml``記述でのインテンド上げ下げは正確に行わければいけないので注意しましょう、インテンドが間違っていると必ずエラーが出てうまくいきません。

imageはDockerイメージの指定になります。
このコンテナではNginxを使いたいのでバージョンを含めて上記のように指定します。

portsは前述の通り、ポートフォワーディングの設定になります。
1で設定したものと同じになるように設定しましょう。
ちなみにホストのポート:ゲストのポートという記述になりますが、ゲスト側のポートの下2桁の00は省略します。

volumesはホスト・コンテナ間でのファイル共有の指定で、ホスト側のパス:コンテナ側のパスの形式で記述します。
Homestead使ったことがある人ならわかると思うのですが、要はホスト側で編集した内容等がゲスト側にも反映されるように同期をとるようなものです。
いちいちゲスト側の環境でコード書いたりしなくてもいいようにすると思っていただければ。

`./docker/web/default.conf:/etc/nginx/conf.d/default.conf`

と書いてありますがこれは`ホスト側のパス:ゲスト側のパス`という記述になります。

`.:/var/www/html`

つまり上記の場合はホスト側のルートフォルダの内容はゲスト側の`/var/www/html`部分に反省されていますよということになります。

また、volumesを設定することファイルやディレクトリは、永続化（コンテナを削除してもホスト側にファイルやディレクトリが残る）させることができるようです。

#### 3.default.confの記述

このファイルはNginxの設定ファイルとなります。
以下のように基準してください。

```
server {
    listen 80;
    root  /var/www/html;
    index index.html;
    access_log /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;
}

```

listenはリクエストを受け付けるIPアドレスやポート番号の設定です。
上記の場合はすべてのIPアドレスの80番ポートでリクエストを受け付けるということになります。
root, indexでは、それぞれ、ドキュメントルートのディレクトリ・インデックスとして使われるファイル名を定義します。
indexの部分は先程の通りでindexは適当な確認用の``index.html``作っておいてそれを設定してください。
access_log, error_logはそれぞれのログ出力先の設定となります。

#### 4.Nginxの動作確認

ここまでの工程でローカルでWebサーバーを立ち上げる準備ができたので立ち上げてみます。
ディレクトリをルートフォルダに移して以下のコマンドを実行します。

```
docker-compose up -d
```

このコマンドは``docker-compose.yml``の記述どおりにコンテナを作って、起動してくださいという意味になります。
``-d``のオプションはバックグラウンドでコンテナを実行するためのコマンドになります。
これがない場合は、``up``で実行された時にコンテナはすぐに終了してしまいます。

無事に作成・開始が成功すると以下のように表示されます。

```
Creating network "docker-compose-laravel_default" with the default driver
Creating docker-compose-laravel_web_1 ... done
```
ブラウザでhttp://localhost:8000 にアクセスすると、index.htmlが表示されます。
8000の部分は設定したポート番号に合わせてください。

####5.PHP実行環境の準備

NginxでWebサーバーを立ち上げたので今度はそこでPHPを動作できるようにします。
そのためにはPHP-FPMというのものを使うのですが、このあたりの詳しいことは参考記事1で紹介されている、以下の記事をご覧になってください。私も今読んでいます。
[nginx と PHP-FPM の仕組みをちゃんと理解しながら PHP の実行環境を構築する](https://qiita.com/kotarella1110/items/634f6fafeb33ae0f51dc#php-fpm-%E3%81%A3%E3%81%A6%E4%BD%95)

さて、コンテナを追加するのでdocker-compose.ymlを編集していきます。
以下の通りに記述してみてください。

```yaml
version: '3'
services:
  web:
    image: nginx:1.15.6
    ports:
      - '8000:80'
    depends_on:
      - app
    volumes:
      - ./docker/web/default.conf:/etc/nginx/conf.d/default.conf
      - .:/var/www/html
  app:
    image: php:7.2-fpm
    volumes:
      - .:/var/www/html

```

servicesの項目に、appを追加します。
そして、webの項目にdepends_onを追加してそこにappを指定しておきます。
これはサービスの依存関係を表すもので、今回はNginxがPHPに依存をしているということになります。
この定義によって、Dockerはコンテナの起動時にサービスの依存関係に基づいてコンテナを起動するようになるので、PHPコンテナが起動した後にNginxコンテナが起動するということになります。
あとの項目はNginxのときと同じですね、イメージを指定してファイル共有の定義をします。

次に、PHPが動くようにNginxの設定を変更します。
以下のように記述してください。

```

server {
    listen 80;
    root /var/www/html;
    index index.php index.html index.htm;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

```

indexの項目を見るとindex.phpとindex.htmが追加されています。
先程はindex.htmlをインデックスページとして設定しましたが、このよう複数設定すると先に記述があるファイルから優先してインデックスページに設定されます。
今回はphpを実行したいのでindex.phpを先頭に記述することになります。

locationの項目はURIごとにどのファイルを配信するのか設定する項目です。
最初のlocationではURIのパスにファイルがあるかどうかを確認し、なかった場合はディレクトリがあるかを確認し、いずれもなかった場合はindex.phpを返すという意味になります。
次のlocationではNginxがPHP-FPMにリクエストを渡すための設定をしています。
詳しいことは先述の記事を確認してください。

ここまでが終わったら動作確認です。
index.htmlと同じように適当な確認用のindex.phpを作成して、配置してください。
そのあとは先程と同じように`docker-compose up -d`を行います。
もし、先程実行したまま作業を続けている場合は、`docker-compose down`というコンテナを停止・削除するコマンドを実行してから行ってください。
実行するとhttp://localhost:8000 でindex.phpが表示されます。
`phpinfo();`のコマンドを仕込んでおくとPHPが実行されているかよりわかりやすくなると思います。

####6.MySQLコンテナを用意する

最後にDBとしてMySQLを準備をします。
docker-compose.ymlを編集してMySQLのコンテナを定義します。

```yml

version: '3'
services:
  web:
    image: nginx:1.15.6
    ports:
      - '8000:80'
    depends_on:
      - app
    volumes:
      - ./docker/web/default.conf:/etc/nginx/conf.d/default.conf
      - .:/var/www/html
  app:
    image: php:7.2-fpm
    volumes:
      - .:/var/www/html
    depends_on:
      - mysql
  mysql:
    image: mysql:5.7
    environment:
      MYSQL_DATABASE: sample
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: password
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
volumes:
  mysql-data:

```

ここまで来るとservicesはweb、app、mysqlで構成されたサービスだということがわかりやすくなりますね。

environmentはMySQLコンテナでの環境変数の設定です。
Laravelでの.envファイルにあるMySQLの項目と同じですね。
またここでのvolumesはmysql-dataをサービス内で共通化して、他のコンテナからも参照できるようにするための設定のようです。

ここまで終わったら動作確認をしましょう。
upしたままならdownをしてから、もう一度`docker-compose up -d`を行います。
そうしたら以下のコマンドでMySQLのコンテナに入ることができます。

```
docker-compose exec mysql bash
```

コンテナに入るとあとは通常のMySQLの操作と同じなので

```
mysql -h localhost -u -p

```

でログインしてください、ユーザー名とパスワードは先程設定したものを入力します。
MySQLに入ったら`show databases;`でデータベースを確認し、docker-compose.ymlで定義したデータベースが作成できているか確認してください。


####7.Laravelのインストール

では、いよいよLaravelのインストールになります。
Composerがどうのとかそういうのはここでは割愛します。
一応私も手前味噌ですがLaravel導入に関して備忘録を残してありますので、よろしければそちらをご覧になってください。

まず、Dockerfileというものを作成します。
これはビルドするイメージの構成の定義です。
docker-compose.ymlで定義したのでは？ と思った方、私も思いました。
どうやらこちらは必要なパッケージその他を指定した上でイメージをどう構成するかということを定義するもののようです。
先程だけだとPHPしか入りませんが、今度はComposerなどを入れたいのでそのためにDockerfileで指示書を書くというイメージでしょうか。

```
FROM php:7.2-fpm
# install composer
RUN cd /usr/bin && curl -s http://getcomposer.org/installer | php && ln -s /usr/bin/composer.phar /usr/bin/composer
RUN apt-get update \
&& apt-get install -y \
git \
zip \
unzip \
vim
RUN apt-get update \
    && apt-get install -y libpq-dev \
    && docker-php-ext-install pdo_mysql pdo_pgsql
WORKDIR /var/www/html
```

これが今回のDockerfileです。
FROMでイメージ(厳密にはDocker Hubレジストリで公開されているベース)を指定し、
curlコマンドでcomposerをインストール、さらにapt-getコマンドでgit、zip、unzip、vimをインストール、最後にdocker-php-ext-installコマンドでPDOをインストールするという意味になります。
RUNでこれらをDockerイメージのビルド時にコンテナで実行するように定義しています。
WORKDIRはRUNなどの命令が実行される際のディレクトリを指定します。

Dockerfileを作ったら、docker-compose.ymlのPHPの部分、つまりappの部分をDockerfileに基づくように修正します。

```yml
version: '3'
services:
  web:
    image: nginx:1.15.6
    ports:
      - '8000:80'
    depends_on:
      - app
    volumes:
      - ./docker/web/default.conf:/etc/nginx/conf.d/default.conf
      - .:/var/www/html
  app:
    build: ./docker/php
    volumes:
      - .:/var/www/html
    depends_on:
      - mysql
  mysql:
    image: mysql:5.7
    environment:
      MYSQL_DATABASE: sample
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: password
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
volumes:
  mysql-data:

```
imageの部分をbuildにしてDockerfileがあるディレクトリを指定します。
こうすることでDockerfileに基づいてイメージを作成し、appコンテナ(PHPコンテナ)が出来上がるということになります。

あとはLaravelを入れるだけです。
毎度のように`docker-compose up -d`を行い、今度はPHPコンテナに入りたいので`docker-compose exec app bash`を行って以下のコマンドを実行してLaravelをインストールします。

```
composer create-project --prefer-dist laravel/laravel プロジェクト名
```

無事にLaravelのインストールが完了したら.envファイルのMySQLの項目をdocker-compose.ymlで定義したものに合わせて設定してください。
DB_HOSTの項目はmysqlと指定します。

最後にNginxの設定をLaravelに合わせたものに修正します。
以下の通りです。

```
server {
    listen 80;
    root /var/www/html/my-laravel-app/public;
    index index.php index.html index.htm;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }
    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

```

rootの部分をLaravelのpubilcフォルダに合わせることでここがルートディレクトリになります。

ここまで終わったら動作確認です。
まず、`docker-compose down `を実行して、`docker-compose up -d`、`docker-compose exec app bash`でPHPコンテナに入ってマイグレーションを行います。
あとは以下のようにLaravelプロジェクトのフォルダにディレクトリを移してmigrateするだけですね。

```
cd プロジェクト名
php artisan migrate
```

マイグレーションが正常に行われたことを確認したら、再度docker-compose down `を実行して、`docker-compose up -d`を行い、http://localhost:8000 を確認してください。
Laravelのウェルカムページが表示できたら無事終了です、お疲れさまでした。

#### おまけ

Docker Toolboxのアップグレードについてはこちらを参照してください。

参考:[Windows10 Homeで導入したdockerを最新にアップデートする方法](https://qiita.com/Seiga/items/e136a1e0bd9ff5852b21)

手順としてはアップグレードしたいバージョンのDocker ToolBoxをダウンロードしてきて、導入の時と同じことをするだけです。
Select Componentsは導入と同じように入れる必要のないもの(この場合はアップグレードする必要のないもの)はチェックを外し、次のSelect Additional Tasksもすべてチェックを外します。

インストールが終わったら`docker -v`でバージョンを確認して、バージョンが上がったことを確認したら`docker-machine upgrade default`で仮想マシン側のDockerもバージョンを上げておきます。(厳密にはdocker-machineは仮想マシン側でのDockerのホストを生成して管理しているものみたいです)
これでアップグレードは完了です。


## あとがき

最初はGit経由でやろうとしていましたが、ポートフォワーディングの罠にハマりうまく行かないと思い込んでしまったので今回Dockerfileとdocker-compose.ymlで1からDocker環境を構築してみました。
災い転じて福となすとばかりに、とてもわかりやすい記事がありましたのでそれを参照しながら、うまく行かない原因がポートフォワーディングの罠だったことなど色々発見しながらDockerにおける環境構築の基本を学習できたのでとても有意義でした。
でも、フォロワーさんに「Linuxを扱えるようにならないとね？(威圧)」というありがたくも耳が痛すぎるリプライを頂いて、それに納得・承知をした上でやっぱり言わせてください。

**Docker環境を整備したい！ という方はできればMacも導入してください。**

Linux、いずれは向き合わないといけないなぁ。